diff --git a/client/components/login.vue b/client/components/login.vue
index 340134cc..5c216df2 100644
--- a/client/components/login.vue
+++ b/client/components/login.vue
@@ -253,6 +253,10 @@ export default {
hideLocal: {
type: Boolean,
default: false
+ },
+ changePwdContinuationToken: {
+ type: String,
+ default: null
}
},
data () {
@@ -309,6 +313,9 @@ export default {
},
selectedStrategyKey (newValue, oldValue) {
this.selectedStrategy = _.find(this.strategies, ['key', newValue])
+ if (this.screen === 'changePwd') {
+ return
+ }
this.screen = 'login'
if (!this.selectedStrategy.strategy.useForm) {
this.isLoading = true
@@ -322,6 +329,10 @@ export default {
},
mounted () {
this.isShown = true
+ if (this.changePwdContinuationToken) {
+ this.screen = 'changePwd'
+ this.continuationToken = this.changePwdContinuationToken
+ }
},
methods: {
/**
@@ -475,32 +486,51 @@ export default {
this.loaderColor = 'grey darken-4'
this.loaderTitle = this.$t('auth:changePwd.loading')
this.isLoading = true
- const resp = await this.$apollo.mutate({
- mutation: gql`
- {
- authentication {
- activeStrategies {
- key
+ try {
+ const resp = await this.$apollo.mutate({
+ mutation: gql`
+ mutation (
+ $continuationToken: String!
+ $newPassword: String!
+ ) {
+ authentication {
+ loginChangePassword (
+ continuationToken: $continuationToken
+ newPassword: $newPassword
+ ) {
+ responseResult {
+ succeeded
+ errorCode
+ slug
+ message
+ }
+ jwt
+ continuationToken
+ redirect
+ }
}
}
+ `,
+ variables: {
+ continuationToken: this.continuationToken,
+ newPassword: this.newPassword
}
- `,
- variables: {
- continuationToken: this.continuationToken,
- newPassword: this.newPassword
+ })
+ if (_.has(resp, 'data.authentication.loginChangePassword')) {
+ let respObj = _.get(resp, 'data.authentication.loginChangePassword', {})
+ if (respObj.responseResult.succeeded === true) {
+ this.handleLoginResponse(respObj)
+ } else {
+ throw new Error(respObj.responseResult.message)
+ }
+ } else {
+ throw new Error(this.$t('auth:genericError'))
}
- })
- if (_.get(resp, 'data.authentication.loginChangePassword.responseResult.succeeded', false) === true) {
- this.loaderColor = 'green darken-1'
- this.loaderTitle = this.$t('auth:loginSuccess')
- Cookies.set('jwt', _.get(resp, 'data.authentication.loginChangePassword.jwt', ''), { expires: 365 })
- _.delay(() => {
- window.location.replace('/') // TEMPORARY - USE RETURNURL
- }, 1000)
- } else {
+ } catch (err) {
+ console.error(err)
this.$store.commit('showNotification', {
style: 'red',
- message: _.get(resp, 'data.authentication.loginChangePassword.responseResult.message', false),
+ message: err.message,
icon: 'alert'
})
this.isLoading = false
@@ -519,11 +549,57 @@ export default {
* FORGOT PASSWORD SUBMIT
*/
async forgotPasswordSubmit () {
- this.$store.commit('showNotification', {
- style: 'pink',
- message: 'Coming soon!',
- icon: 'ferry'
- })
+ this.loaderColor = 'grey darken-4'
+ this.loaderTitle = this.$t('auth:forgotPasswordLoading')
+ this.isLoading = true
+ try {
+ const resp = await this.$apollo.mutate({
+ mutation: gql`
+ mutation (
+ $email: String!
+ ) {
+ authentication {
+ forgotPassword (
+ email: $email
+ ) {
+ responseResult {
+ succeeded
+ errorCode
+ slug
+ message
+ }
+ }
+ }
+ }
+ `,
+ variables: {
+ email: this.username
+ }
+ })
+ if (_.has(resp, 'data.authentication.forgotPassword.responseResult')) {
+ let respObj = _.get(resp, 'data.authentication.forgotPassword.responseResult', {})
+ if (respObj.succeeded === true) {
+ this.$store.commit('showNotification', {
+ style: 'success',
+ message: this.$t('auth:forgotPasswordSuccess'),
+ icon: 'email'
+ })
+ this.screen = 'login'
+ } else {
+ throw new Error(respObj.message)
+ }
+ } else {
+ throw new Error(this.$t('auth:genericError'))
+ }
+ } catch (err) {
+ console.error(err)
+ this.$store.commit('showNotification', {
+ style: 'red',
+ message: err.message,
+ icon: 'alert'
+ })
+ }
+ this.isLoading = false
},
handleLoginResponse (respObj) {
this.continuationToken = respObj.continuationToken
diff --git a/server/controllers/auth.js b/server/controllers/auth.js
index 7be1b491..df204e7e 100644
--- a/server/controllers/auth.js
+++ b/server/controllers/auth.js
@@ -148,6 +148,28 @@ router.get('/verify/:token', bruteforce.prevent, async (req, res, next) => {
}
})
+/**
+ * Reset Password
+ */
+router.get('/login-reset/:token', bruteforce.prevent, async (req, res, next) => {
+ try {
+ const usr = await WIKI.models.userKeys.validateToken({ kind: 'resetPwd', token: req.params.token })
+ if (!usr) {
+ throw new Error('Invalid Token')
+ }
+ req.brute.reset()
+
+ const changePwdContinuationToken = await WIKI.models.userKeys.generateToken({
+ userId: usr.id,
+ kind: 'changePwd'
+ })
+ const bgUrl = !_.isEmpty(WIKI.config.auth.loginBgUrl) ? WIKI.config.auth.loginBgUrl : '/_assets/img/splash/1.jpg'
+ res.render('login', { bgUrl, hideLocal: WIKI.config.auth.hideLocal, changePwdContinuationToken })
+ } catch (err) {
+ next(err)
+ }
+})
+
/**
* JWT Public Endpoints
*/
diff --git a/server/core/mail.js b/server/core/mail.js
index 9362c61d..43cf0141 100644
--- a/server/core/mail.js
+++ b/server/core/mail.js
@@ -56,7 +56,7 @@ module.exports = {
subject: `${opts.subject} - ${WIKI.config.title}`,
text: opts.text,
html: _.get(this.templates, opts.template)({
- logo: '',
+ logo: WIKI.config.logoUrl,
siteTitle: WIKI.config.title,
copyright: WIKI.config.company.length > 0 ? WIKI.config.company : 'Powered by Wiki.js',
...opts.data
diff --git a/server/graph/resolvers/authentication.js b/server/graph/resolvers/authentication.js
index ae72f715..d2ed5d9d 100644
--- a/server/graph/resolvers/authentication.js
+++ b/server/graph/resolvers/authentication.js
@@ -137,6 +137,19 @@ module.exports = {
return graphHelper.generateError(err)
}
},
+ /**
+ * Perform Mandatory Password Change after Login
+ */
+ async forgotPassword (obj, args, context) {
+ try {
+ await WIKI.models.users.loginForgotPassword(args, context)
+ return {
+ responseResult: graphHelper.generateSuccess('Password reset request processed.')
+ }
+ } catch (err) {
+ return graphHelper.generateError(err)
+ }
+ },
/**
* Register a new account
*/
diff --git a/server/graph/schemas/authentication.graphql b/server/graph/schemas/authentication.graphql
index b079cf23..202e82a5 100644
--- a/server/graph/schemas/authentication.graphql
+++ b/server/graph/schemas/authentication.graphql
@@ -52,6 +52,10 @@ type AuthenticationMutation {
newPassword: String!
): AuthenticationLoginResponse @rateLimit(limit: 5, duration: 60)
+ forgotPassword(
+ email: String!
+ ): DefaultResponse @rateLimit(limit: 3, duration: 60)
+
register(
email: String!
password: String!
diff --git a/server/models/users.js b/server/models/users.js
index 89c335c7..411389f6 100644
--- a/server/models/users.js
+++ b/server/models/users.js
@@ -478,6 +478,38 @@ module.exports = class User extends Model {
}
}
+ /**
+ * Send a password reset request
+ */
+ static async loginForgotPassword ({ email }, context) {
+ const usr = await WIKI.models.users.query().where({
+ email,
+ providerKey: 'local'
+ }).first()
+ if (!usr) {
+ WIKI.logger.debug(`Password reset attempt on nonexistant local account ${email}: [DISCARDED]`)
+ return
+ }
+ const resetToken = await WIKI.models.userKeys.generateToken({
+ userId: usr.id,
+ kind: 'resetPwd'
+ })
+
+ await WIKI.mail.send({
+ template: 'accountResetPwd',
+ to: email,
+ subject: `Password Reset Request`,
+ data: {
+ preheadertext: `A password reset was requested for ${WIKI.config.title}`,
+ title: `A password reset was requested for ${WIKI.config.title}`,
+ content: `Click the button below to reset your password. If you didn't request this password reset, simply discard this email.`,
+ buttonLink: `${WIKI.config.host}/login-reset/${resetToken}`,
+ buttonText: 'Reset Password'
+ },
+ text: `A password reset was requested for wiki ${WIKI.config.title}. Open the following link to proceed: ${WIKI.config.host}/login-reset/${resetToken}`
+ })
+ }
+
/**
* Create a new user
*
diff --git a/server/templates/account-reset-pwd.html b/server/templates/account-reset-pwd.html
new file mode 100644
index 00000000..b3d55cc5
--- /dev/null
+++ b/server/templates/account-reset-pwd.html
@@ -0,0 +1,304 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <%= preheadertext %>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+ <%= title %>
+ <%= content %>
+ |
+
+
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+ <%= copyright %>
+ |
+
+
+
+
+
+
+
+
+
+
+
diff --git a/server/views/login.pug b/server/views/login.pug
index 4536a921..c0408cd9 100644
--- a/server/views/login.pug
+++ b/server/views/login.pug
@@ -5,4 +5,5 @@ block body
login(
bg-url=bgUrl
hide-local=hideLocal
+ change-pwd-continuation-token=changePwdContinuationToken
)