mirror of
https://github.com/requarks/wiki.git
synced 2026-08-26 13:17:10 -05:00
feat: login change password step
This commit is contained in:
@@ -735,8 +735,7 @@ export async function up (knex) {
|
||||
auth: {
|
||||
[authModuleId]: {
|
||||
password: await bcrypt.hash(process.env.ADMIN_PASS || '12345678', 12),
|
||||
mustChangePwd: false, // TODO: Revert to true (below) once change password flow is implemented
|
||||
// mustChangePwd: !process.env.ADMIN_PASS,
|
||||
mustChangePwd: !process.env.ADMIN_PASS,
|
||||
restrictLogin: false,
|
||||
tfaIsActive: false,
|
||||
tfaRequired: false,
|
||||
|
||||
@@ -283,42 +283,42 @@ export default {
|
||||
return generateError(err)
|
||||
}
|
||||
},
|
||||
async changePassword (obj, args, context) {
|
||||
try {
|
||||
if (!context.req.user || context.req.user.id < 1 || context.req.user.id === 2) {
|
||||
throw new WIKI.Error.AuthRequired()
|
||||
}
|
||||
const usr = await WIKI.db.users.query().findById(context.req.user.id)
|
||||
if (!usr.isActive) {
|
||||
throw new WIKI.Error.AuthAccountBanned()
|
||||
}
|
||||
if (!usr.isVerified) {
|
||||
throw new WIKI.Error.AuthAccountNotVerified()
|
||||
}
|
||||
if (usr.providerKey !== 'local') {
|
||||
throw new WIKI.Error.AuthProviderInvalid()
|
||||
}
|
||||
try {
|
||||
await usr.verifyPassword(args.current)
|
||||
} catch (err) {
|
||||
throw new WIKI.Error.AuthPasswordInvalid()
|
||||
}
|
||||
// async changePassword (obj, args, context) {
|
||||
// try {
|
||||
// if (!context.req.user || context.req.user.id < 1 || context.req.user.id === 2) {
|
||||
// throw new WIKI.Error.AuthRequired()
|
||||
// }
|
||||
// const usr = await WIKI.db.users.query().findById(context.req.user.id)
|
||||
// if (!usr.isActive) {
|
||||
// throw new WIKI.Error.AuthAccountBanned()
|
||||
// }
|
||||
// if (!usr.isVerified) {
|
||||
// throw new WIKI.Error.AuthAccountNotVerified()
|
||||
// }
|
||||
// if (usr.providerKey !== 'local') {
|
||||
// throw new WIKI.Error.AuthProviderInvalid()
|
||||
// }
|
||||
// try {
|
||||
// await usr.verifyPassword(args.current)
|
||||
// } catch (err) {
|
||||
// throw new WIKI.Error.AuthPasswordInvalid()
|
||||
// }
|
||||
|
||||
await WIKI.db.users.updateUser({
|
||||
id: usr.id,
|
||||
newPassword: args.new
|
||||
})
|
||||
// await WIKI.db.users.updateUser({
|
||||
// id: usr.id,
|
||||
// newPassword: args.new
|
||||
// })
|
||||
|
||||
const newToken = await WIKI.db.users.refreshToken(usr)
|
||||
// const newToken = await WIKI.db.users.refreshToken(usr)
|
||||
|
||||
return {
|
||||
responseResult: generateSuccess('Password changed successfully'),
|
||||
jwt: newToken.token
|
||||
}
|
||||
} catch (err) {
|
||||
return generateError(err)
|
||||
}
|
||||
},
|
||||
// return {
|
||||
// responseResult: generateSuccess('Password changed successfully'),
|
||||
// jwt: newToken.token
|
||||
// }
|
||||
// } catch (err) {
|
||||
// return generateError(err)
|
||||
// }
|
||||
// },
|
||||
/**
|
||||
* UPLOAD USER AVATAR
|
||||
*/
|
||||
|
||||
@@ -1129,6 +1129,7 @@
|
||||
"auth.changePwd.newPasswordVerify": "Verify New Password",
|
||||
"auth.changePwd.proceed": "Change Password",
|
||||
"auth.changePwd.subtitle": "Choose a new password",
|
||||
"auth.changePwd.success": "Password updated successfully.",
|
||||
"auth.enterCredentials": "Enter your credentials",
|
||||
"auth.errors.forgotPassword": "Missing or invalid email address.",
|
||||
"auth.errors.invalidEmail": "Email is invalid.",
|
||||
@@ -1198,6 +1199,7 @@
|
||||
"auth.tfaSetupInstrSecond": "Enter the security code generated from your trusted device:",
|
||||
"auth.tfaSetupTitle": "Your administrator has required Two-Factor Authentication (2FA) to be enabled on your account.",
|
||||
"auth.tfaSetupVerifying": "Verifying...",
|
||||
"auth.tfaSetupSuccess": "2FA enabled successfully on your account.",
|
||||
"common.actions.activate": "Activate",
|
||||
"common.actions.add": "Add",
|
||||
"common.actions.apply": "Apply",
|
||||
|
||||
@@ -66,14 +66,14 @@ export class UserKey extends Model {
|
||||
await WIKI.db.userKeys.query().deleteById(res.id)
|
||||
}
|
||||
if (DateTime.utc() > DateTime.fromISO(res.validUntil)) {
|
||||
throw new WIKI.Error.AuthValidationTokenInvalid()
|
||||
throw new Error('ERR_EXPIRED_VALIDATION_TOKEN')
|
||||
}
|
||||
return {
|
||||
...res.meta,
|
||||
user: res.user
|
||||
}
|
||||
} else {
|
||||
throw new WIKI.Error.AuthValidationTokenInvalid()
|
||||
throw new Error('ERR_INVALID_VALIDATION_TOKEN')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+35
-22
@@ -325,7 +325,10 @@ export class User extends Model {
|
||||
try {
|
||||
const tfaToken = await WIKI.db.userKeys.generateToken({
|
||||
kind: 'tfa',
|
||||
userId: user.id
|
||||
userId: user.id,
|
||||
meta: {
|
||||
strategyId
|
||||
}
|
||||
})
|
||||
return {
|
||||
nextAction: 'provideTfa',
|
||||
@@ -341,7 +344,10 @@ export class User extends Model {
|
||||
const tfaQRImage = await user.generateTFA(strategyId, siteId)
|
||||
const tfaToken = await WIKI.db.userKeys.generateToken({
|
||||
kind: 'tfaSetup',
|
||||
userId: user.id
|
||||
userId: user.id,
|
||||
meta: {
|
||||
strategyId
|
||||
}
|
||||
})
|
||||
return {
|
||||
nextAction: 'setupTfa',
|
||||
@@ -361,7 +367,10 @@ export class User extends Model {
|
||||
try {
|
||||
const pwdChangeToken = await WIKI.db.userKeys.generateToken({
|
||||
kind: 'changePwd',
|
||||
userId: user.id
|
||||
userId: user.id,
|
||||
meta: {
|
||||
strategyId
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
@@ -435,11 +444,16 @@ export class User extends Model {
|
||||
*/
|
||||
static async loginTFA ({ strategyId, siteId, securityCode, continuationToken, setup }, context) {
|
||||
if (securityCode.length === 6 && continuationToken.length > 1) {
|
||||
const { user } = await WIKI.db.userKeys.validateToken({
|
||||
const { user, strategyId: expectedStrategyId } = await WIKI.db.userKeys.validateToken({
|
||||
kind: setup ? 'tfaSetup' : 'tfa',
|
||||
token: continuationToken,
|
||||
skipDelete: setup
|
||||
})
|
||||
|
||||
if (strategyId !== expectedStrategyId) {
|
||||
throw new Error('ERR_UNEXPECTED_STRATEGY_ID')
|
||||
}
|
||||
|
||||
if (user) {
|
||||
if (user.verifyTFA(strategyId, securityCode)) {
|
||||
if (setup) {
|
||||
@@ -447,40 +461,39 @@ export class User extends Model {
|
||||
}
|
||||
return WIKI.db.users.afterLoginChecks(user, strategyId, context, { siteId, skipTFA: true })
|
||||
} else {
|
||||
throw new WIKI.Error.AuthTFAFailed()
|
||||
throw new Error('ERR_INCORRECT_TFA_TOKEN')
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new WIKI.Error.AuthTFAInvalid()
|
||||
throw new Error('ERR_INVALID_TFA_REQUEST')
|
||||
}
|
||||
|
||||
/**
|
||||
* Change Password from a Mandatory Password Change after Login
|
||||
*/
|
||||
static async loginChangePassword ({ continuationToken, newPassword }, context) {
|
||||
if (!newPassword || newPassword.length < 6) {
|
||||
throw new WIKI.Error.InputInvalid('Password must be at least 6 characters!')
|
||||
static async loginChangePassword ({ strategyId, siteId, continuationToken, newPassword }, context) {
|
||||
if (!newPassword || newPassword.length < 8) {
|
||||
throw new Error('ERR_PASSWORD_TOO_SHORT')
|
||||
}
|
||||
const usr = await WIKI.db.userKeys.validateToken({
|
||||
const { user, strategyId: expectedStrategyId } = await WIKI.db.userKeys.validateToken({
|
||||
kind: 'changePwd',
|
||||
token: continuationToken
|
||||
})
|
||||
|
||||
if (usr) {
|
||||
await WIKI.db.users.query().patch({
|
||||
password: newPassword,
|
||||
mustChangePwd: false
|
||||
}).findById(usr.id)
|
||||
if (strategyId !== expectedStrategyId) {
|
||||
throw new Error('ERR_UNEXPECTED_STRATEGY_ID')
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
context.req.logIn(usr, { session: false }, async err => {
|
||||
if (err) { return reject(err) }
|
||||
const jwtToken = await WIKI.db.users.refreshToken(usr)
|
||||
resolve({ jwt: jwtToken.token })
|
||||
})
|
||||
if (user) {
|
||||
user.auth[strategyId].password = await bcrypt.hash(newPassword, 12),
|
||||
user.auth[strategyId].mustChangePwd = false
|
||||
await user.$query().patch({
|
||||
auth: user.auth
|
||||
})
|
||||
|
||||
return WIKI.db.users.afterLoginChecks(user, strategyId, context, { siteId, skipChangePwd: true, skipTFA: true })
|
||||
} else {
|
||||
throw new WIKI.Error.UserNotFound()
|
||||
throw new Error('ERR_INVALID_USER')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,18 +21,20 @@ export default {
|
||||
if (user) {
|
||||
const authStrategyData = user.auth[strategyId]
|
||||
if (!authStrategyData) {
|
||||
throw new WIKI.Error.AuthLoginFailed()
|
||||
throw new Error('ERR_INVALID_STRATEGY_ID')
|
||||
} else if (await bcrypt.compare(uPassword, authStrategyData.password) !== true) {
|
||||
throw new WIKI.Error.AuthLoginFailed()
|
||||
throw new Error('ERR_AUTH_FAILED')
|
||||
} else if (!user.isActive) {
|
||||
throw new WIKI.Error.AuthAccountBanned()
|
||||
throw new Error('ERR_INACTIVE_USER')
|
||||
} else if (authStrategyData.restrictLogin) {
|
||||
throw new Error('ERR_LOGIN_RESTRICTED')
|
||||
} else if (!user.isVerified) {
|
||||
throw new WIKI.Error.AuthAccountNotVerified()
|
||||
throw new Error('ERR_USER_NOT_VERIFIED')
|
||||
} else {
|
||||
done(null, user)
|
||||
}
|
||||
} else {
|
||||
throw new WIKI.Error.AuthLoginFailed()
|
||||
throw new Error('ERR_AUTH_FAILED')
|
||||
}
|
||||
} catch (err) {
|
||||
done(err, null)
|
||||
|
||||
@@ -620,6 +620,11 @@ async function forgotPassword () {
|
||||
if (!isFormValid) {
|
||||
throw new Error(t('auth.errors.forgotPassword'))
|
||||
}
|
||||
// TODO: Implement forgot password
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: 'Not implemented yet.'
|
||||
})
|
||||
} catch (err) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
@@ -695,17 +700,13 @@ async function changePwd () {
|
||||
const resp = await APOLLO_CLIENT.mutate({
|
||||
mutation: gql`
|
||||
mutation (
|
||||
$userId: UUID!
|
||||
$continuationToken: String
|
||||
$currentPassword: String
|
||||
$newPassword: String!
|
||||
$strategyId: UUID!
|
||||
$siteId: UUID
|
||||
) {
|
||||
changePassword (
|
||||
userId: $userId
|
||||
continuationToken: $continuationToken
|
||||
currentPassword: $currentPassword
|
||||
newPassword: $newPassword
|
||||
strategyId: $strategyId
|
||||
siteId: $siteId
|
||||
@@ -715,9 +716,7 @@ async function changePwd () {
|
||||
message
|
||||
}
|
||||
jwt
|
||||
mustChangePwd
|
||||
mustProvideTFA
|
||||
mustSetupTFA
|
||||
nextAction
|
||||
continuationToken
|
||||
redirect
|
||||
tfaQRImage
|
||||
@@ -725,19 +724,21 @@ async function changePwd () {
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
userId: userStore.id,
|
||||
continuationToken: state.continuationToken,
|
||||
currentPassword: state.password,
|
||||
newPassword: state.newPassword,
|
||||
strategyId: state.selectedStrategyId,
|
||||
siteId: siteStore.id
|
||||
}
|
||||
})
|
||||
if (resp.data?.login?.operation?.succeeded) {
|
||||
if (resp.data?.changePassword?.operation?.succeeded) {
|
||||
state.password = ''
|
||||
await handleLoginResponse(resp.data.login)
|
||||
$q.notify({
|
||||
type: 'positive',
|
||||
message: t('auth.changePwd.success')
|
||||
})
|
||||
await handleLoginResponse(resp.data.changePassword)
|
||||
} else {
|
||||
throw new Error(resp.data?.login?.operation?.message || t('auth.errors.loginError'))
|
||||
throw new Error(resp.data?.changePassword?.operation?.message || t('auth.errors.loginError'))
|
||||
}
|
||||
} catch (err) {
|
||||
$q.notify({
|
||||
@@ -855,6 +856,10 @@ async function finishSetupTFA () {
|
||||
if (resp.data?.loginTFA?.operation?.succeeded) {
|
||||
state.continuationToken = ''
|
||||
state.securityCode = ''
|
||||
$q.notify({
|
||||
type: 'positive',
|
||||
message: t('auth.tfaSetupSuccess')
|
||||
})
|
||||
await handleLoginResponse(resp.data.loginTFA)
|
||||
} else {
|
||||
throw new Error(resp.data?.loginTFA?.operation?.message || t('auth.errors.loginError'))
|
||||
|
||||
Reference in New Issue
Block a user