Skip to content

Commit ae73339

Browse files
committed
feat: password reset
1 parent f98a07e commit ae73339

8 files changed

Lines changed: 478 additions & 26 deletions

File tree

client/components/login.vue

Lines changed: 101 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,10 @@ export default {
253253
hideLocal: {
254254
type: Boolean,
255255
default: false
256+
},
257+
changePwdContinuationToken: {
258+
type: String,
259+
default: null
256260
}
257261
},
258262
data () {
@@ -309,6 +313,9 @@ export default {
309313
},
310314
selectedStrategyKey (newValue, oldValue) {
311315
this.selectedStrategy = _.find(this.strategies, ['key', newValue])
316+
if (this.screen === 'changePwd') {
317+
return
318+
}
312319
this.screen = 'login'
313320
if (!this.selectedStrategy.strategy.useForm) {
314321
this.isLoading = true
@@ -322,6 +329,10 @@ export default {
322329
},
323330
mounted () {
324331
this.isShown = true
332+
if (this.changePwdContinuationToken) {
333+
this.screen = 'changePwd'
334+
this.continuationToken = this.changePwdContinuationToken
335+
}
325336
},
326337
methods: {
327338
/**
@@ -475,32 +486,51 @@ export default {
475486
this.loaderColor = 'grey darken-4'
476487
this.loaderTitle = this.$t('auth:changePwd.loading')
477488
this.isLoading = true
478-
const resp = await this.$apollo.mutate({
479-
mutation: gql`
480-
{
481-
authentication {
482-
activeStrategies {
483-
key
489+
try {
490+
const resp = await this.$apollo.mutate({
491+
mutation: gql`
492+
mutation (
493+
$continuationToken: String!
494+
$newPassword: String!
495+
) {
496+
authentication {
497+
loginChangePassword (
498+
continuationToken: $continuationToken
499+
newPassword: $newPassword
500+
) {
501+
responseResult {
502+
succeeded
503+
errorCode
504+
slug
505+
message
506+
}
507+
jwt
508+
continuationToken
509+
redirect
510+
}
484511
}
485512
}
513+
`,
514+
variables: {
515+
continuationToken: this.continuationToken,
516+
newPassword: this.newPassword
486517
}
487-
`,
488-
variables: {
489-
continuationToken: this.continuationToken,
490-
newPassword: this.newPassword
518+
})
519+
if (_.has(resp, 'data.authentication.loginChangePassword')) {
520+
let respObj = _.get(resp, 'data.authentication.loginChangePassword', {})
521+
if (respObj.responseResult.succeeded === true) {
522+
this.handleLoginResponse(respObj)
523+
} else {
524+
throw new Error(respObj.responseResult.message)
525+
}
526+
} else {
527+
throw new Error(this.$t('auth:genericError'))
491528
}
492-
})
493-
if (_.get(resp, 'data.authentication.loginChangePassword.responseResult.succeeded', false) === true) {
494-
this.loaderColor = 'green darken-1'
495-
this.loaderTitle = this.$t('auth:loginSuccess')
496-
Cookies.set('jwt', _.get(resp, 'data.authentication.loginChangePassword.jwt', ''), { expires: 365 })
497-
_.delay(() => {
498-
window.location.replace('/') // TEMPORARY - USE RETURNURL
499-
}, 1000)
500-
} else {
529+
} catch (err) {
530+
console.error(err)
501531
this.$store.commit('showNotification', {
502532
style: 'red',
503-
message: _.get(resp, 'data.authentication.loginChangePassword.responseResult.message', false),
533+
message: err.message,
504534
icon: 'alert'
505535
})
506536
this.isLoading = false
@@ -519,11 +549,57 @@ export default {
519549
* FORGOT PASSWORD SUBMIT
520550
*/
521551
async forgotPasswordSubmit () {
522-
this.$store.commit('showNotification', {
523-
style: 'pink',
524-
message: 'Coming soon!',
525-
icon: 'ferry'
526-
})
552+
this.loaderColor = 'grey darken-4'
553+
this.loaderTitle = this.$t('auth:forgotPasswordLoading')
554+
this.isLoading = true
555+
try {
556+
const resp = await this.$apollo.mutate({
557+
mutation: gql`
558+
mutation (
559+
$email: String!
560+
) {
561+
authentication {
562+
forgotPassword (
563+
email: $email
564+
) {
565+
responseResult {
566+
succeeded
567+
errorCode
568+
slug
569+
message
570+
}
571+
}
572+
}
573+
}
574+
`,
575+
variables: {
576+
email: this.username
577+
}
578+
})
579+
if (_.has(resp, 'data.authentication.forgotPassword.responseResult')) {
580+
let respObj = _.get(resp, 'data.authentication.forgotPassword.responseResult', {})
581+
if (respObj.succeeded === true) {
582+
this.$store.commit('showNotification', {
583+
style: 'success',
584+
message: this.$t('auth:forgotPasswordSuccess'),
585+
icon: 'email'
586+
})
587+
this.screen = 'login'
588+
} else {
589+
throw new Error(respObj.message)
590+
}
591+
} else {
592+
throw new Error(this.$t('auth:genericError'))
593+
}
594+
} catch (err) {
595+
console.error(err)
596+
this.$store.commit('showNotification', {
597+
style: 'red',
598+
message: err.message,
599+
icon: 'alert'
600+
})
601+
}
602+
this.isLoading = false
527603
},
528604
handleLoginResponse (respObj) {
529605
this.continuationToken = respObj.continuationToken

server/controllers/auth.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,28 @@ router.get('/verify/:token', bruteforce.prevent, async (req, res, next) => {
148148
}
149149
})
150150

151+
/**
152+
* Reset Password
153+
*/
154+
router.get('/login-reset/:token', bruteforce.prevent, async (req, res, next) => {
155+
try {
156+
const usr = await WIKI.models.userKeys.validateToken({ kind: 'resetPwd', token: req.params.token })
157+
if (!usr) {
158+
throw new Error('Invalid Token')
159+
}
160+
req.brute.reset()
161+
162+
const changePwdContinuationToken = await WIKI.models.userKeys.generateToken({
163+
userId: usr.id,
164+
kind: 'changePwd'
165+
})
166+
const bgUrl = !_.isEmpty(WIKI.config.auth.loginBgUrl) ? WIKI.config.auth.loginBgUrl : '/_assets/img/splash/1.jpg'
167+
res.render('login', { bgUrl, hideLocal: WIKI.config.auth.hideLocal, changePwdContinuationToken })
168+
} catch (err) {
169+
next(err)
170+
}
171+
})
172+
151173
/**
152174
* JWT Public Endpoints
153175
*/

server/core/mail.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ module.exports = {
5656
subject: `${opts.subject} - ${WIKI.config.title}`,
5757
text: opts.text,
5858
html: _.get(this.templates, opts.template)({
59-
logo: '',
59+
logo: WIKI.config.logoUrl,
6060
siteTitle: WIKI.config.title,
6161
copyright: WIKI.config.company.length > 0 ? WIKI.config.company : 'Powered by Wiki.js',
6262
...opts.data

server/graph/resolvers/authentication.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,19 @@ module.exports = {
137137
return graphHelper.generateError(err)
138138
}
139139
},
140+
/**
141+
* Perform Mandatory Password Change after Login
142+
*/
143+
async forgotPassword (obj, args, context) {
144+
try {
145+
await WIKI.models.users.loginForgotPassword(args, context)
146+
return {
147+
responseResult: graphHelper.generateSuccess('Password reset request processed.')
148+
}
149+
} catch (err) {
150+
return graphHelper.generateError(err)
151+
}
152+
},
140153
/**
141154
* Register a new account
142155
*/

server/graph/schemas/authentication.graphql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ type AuthenticationMutation {
5252
newPassword: String!
5353
): AuthenticationLoginResponse @rateLimit(limit: 5, duration: 60)
5454

55+
forgotPassword(
56+
email: String!
57+
): DefaultResponse @rateLimit(limit: 3, duration: 60)
58+
5559
register(
5660
email: String!
5761
password: String!

server/models/users.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,38 @@ module.exports = class User extends Model {
478478
}
479479
}
480480

481+
/**
482+
* Send a password reset request
483+
*/
484+
static async loginForgotPassword ({ email }, context) {
485+
const usr = await WIKI.models.users.query().where({
486+
email,
487+
providerKey: 'local'
488+
}).first()
489+
if (!usr) {
490+
WIKI.logger.debug(`Password reset attempt on nonexistant local account ${email}: [DISCARDED]`)
491+
return
492+
}
493+
const resetToken = await WIKI.models.userKeys.generateToken({
494+
userId: usr.id,
495+
kind: 'resetPwd'
496+
})
497+
498+
await WIKI.mail.send({
499+
template: 'accountResetPwd',
500+
to: email,
501+
subject: `Password Reset Request`,
502+
data: {
503+
preheadertext: `A password reset was requested for ${WIKI.config.title}`,
504+
title: `A password reset was requested for ${WIKI.config.title}`,
505+
content: `Click the button below to reset your password. If you didn't request this password reset, simply discard this email.`,
506+
buttonLink: `${WIKI.config.host}/login-reset/${resetToken}`,
507+
buttonText: 'Reset Password'
508+
},
509+
text: `A password reset was requested for wiki ${WIKI.config.title}. Open the following link to proceed: ${WIKI.config.host}/login-reset/${resetToken}`
510+
})
511+
}
512+
481513
/**
482514
* Create a new user
483515
*

0 commit comments

Comments
 (0)