Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wiki-js
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
1
Issues
1
List
Board
Labels
Milestones
Merge Requests
1
Merge Requests
1
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Jacklull
wiki-js
Commits
c5a441c9
Unverified
Commit
c5a441c9
authored
Oct 01, 2023
by
NGPixel
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: login change password step
parent
fe8066c8
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
100 additions
and
79 deletions
+100
-79
3.0.0.mjs
server/db/migrations/3.0.0.mjs
+1
-2
user.mjs
server/graph/resolvers/user.mjs
+36
-36
en.json
server/locales/en.json
+2
-0
userKeys.mjs
server/models/userKeys.mjs
+2
-2
users.mjs
server/models/users.mjs
+35
-22
authentication.mjs
server/modules/authentication/local/authentication.mjs
+7
-5
AuthLoginPanel.vue
ux/src/components/AuthLoginPanel.vue
+17
-12
No files found.
server/db/migrations/3.0.0.mjs
View file @
c5a441c9
...
...
@@ -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
,
...
...
server/graph/resolvers/user.mjs
View file @
c5a441c9
...
...
@@ -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
()
}
await
WIKI
.
db
.
users
.
updateUser
({
id
:
usr
.
id
,
newPassword
:
args
.
new
})
const
newToken
=
await
WIKI
.
db
.
users
.
refreshToken
(
usr
)
return
{
responseResult
:
generateSuccess
(
'Password changed successfully'
),
jwt
:
newToken
.
token
}
}
catch
(
err
)
{
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()
//
}
//
await WIKI.db.users.updateUser({
//
id: usr.id,
//
newPassword: args.new
//
})
//
const newToken = await WIKI.db.users.refreshToken(usr)
//
return {
//
responseResult: generateSuccess('Password changed successfully'),
//
jwt: newToken.token
//
}
//
} catch (err) {
//
return generateError(err)
//
}
//
},
/**
* UPLOAD USER AVATAR
*/
...
...
server/locales/en.json
View file @
c5a441c9
...
...
@@ -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"
,
...
...
server/models/userKeys.mjs
View file @
c5a441c9
...
...
@@ -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'
)
}
}
...
...
server/models/users.mjs
View file @
c5a441c9
...
...
@@ -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'
)
}
}
...
...
server/modules/authentication/local/authentication.mjs
View file @
c5a441c9
...
...
@@ -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
)
...
...
ux/src/components/AuthLoginPanel.vue
View file @
c5a441c9
...
...
@@ -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'
))
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment