Skip to content

Commit

Permalink
feat: setup TFA
Browse files Browse the repository at this point in the history
  • Loading branch information
NGPixel committed Oct 1, 2023
1 parent 7c2b5dd commit fe8066c
Show file tree
Hide file tree
Showing 13 changed files with 383 additions and 273 deletions.
4 changes: 4 additions & 0 deletions server/app/data.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ editors:
wysiwyg:
contentType: html
config: {}
systemIds:
localAuthId: '5a528c4c-0a82-4ad2-96a5-2b23811e6588'
guestsGroupId: '10000000-0000-4000-8000-000000000001'
usersGroupId: '20000000-0000-4000-8000-000000000002'
groups:
defaultPermissions:
- 'read:pages'
Expand Down
8 changes: 2 additions & 6 deletions server/core/auth.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,9 @@ export default {
for (const stg of enabledStrategies) {
try {
const strategy = (await import(`../modules/authentication/${stg.module}/authentication.mjs`)).default
strategy.init(passport, stg.id, stg.config)

stg.config.callbackURL = `${WIKI.config.host}/login/${stg.id}/callback`
stg.config.key = stg.id
strategy.init(passport, stg.config)
strategy.config = stg.config

WIKI.auth.strategies[stg.key] = {
WIKI.auth.strategies[stg.id] = {
...strategy,
...stg
}
Expand Down
17 changes: 11 additions & 6 deletions server/db/migrations/3.0.0.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ export async function up (knex) {
table.boolean('isEnabled').notNullable().defaultTo(false)
table.string('displayName').notNullable().defaultTo('')
table.jsonb('config').notNullable().defaultTo('{}')
table.boolean('selfRegistration').notNullable().defaultTo(false)
table.string('allowedEmailRegex')
table.boolean('registration').notNullable().defaultTo(false)
table.string('allowedEmailRegex').notNullable().defaultTo('')
table.specificType('autoEnrollGroups', 'uuid[]')
})
.createTable('blocks', table => {
Expand Down Expand Up @@ -430,10 +430,10 @@ export async function up (knex) {
// -> GENERATE IDS

const groupAdminId = uuid()
const groupUserId = uuid()
const groupGuestId = '10000000-0000-4000-8000-000000000001'
const groupUserId = WIKI.data.systemIds.usersGroupId
const groupGuestId = WIKI.data.systemIds.guestsGroupId
const siteId = uuid()
const authModuleId = uuid()
const authModuleId = WIKI.data.systemIds.localAuthId
const userAdminId = uuid()
const userGuestId = uuid()

Expand Down Expand Up @@ -719,7 +719,11 @@ export async function up (knex) {
id: authModuleId,
module: 'local',
isEnabled: true,
displayName: 'Local Authentication'
displayName: 'Local Authentication',
config: JSON.stringify({
emailValidation: true,
enforceTfa: false
})
})

// -> USERS
Expand All @@ -734,6 +738,7 @@ export async function up (knex) {
mustChangePwd: false, // TODO: Revert to true (below) once change password flow is implemented
// mustChangePwd: !process.env.ADMIN_PASS,
restrictLogin: false,
tfaIsActive: false,
tfaRequired: false,
tfaSecret: ''
}
Expand Down
18 changes: 11 additions & 7 deletions server/graph/resolvers/authentication.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default {
return {
...a,
config: _.transform(str.props, (r, v, k) => {
r[k] = v.sensitive ? a.config[k] : '********'
r[k] = v.sensitive ? '********' : a.config[k]
}, {})
}
})
Expand Down Expand Up @@ -102,7 +102,7 @@ export default {
if (args.strategy === 'ldap' && WIKI.config.flags.ldapdebug) {
WIKI.logger.warn('LDAP LOGIN ERROR (c1): ', err)
}
console.error(err)
WIKI.logger.debug(err)

return generateError(err)
}
Expand All @@ -115,9 +115,10 @@ export default {
const authResult = await WIKI.db.users.loginTFA(args, context)
return {
...authResult,
responseResult: generateSuccess('TFA success')
operation: generateSuccess('TFA success')
}
} catch (err) {
WIKI.logger.debug(err)
return generateError(err)
}
},
Expand All @@ -129,9 +130,10 @@ export default {
const authResult = await WIKI.db.users.loginChangePassword(args, context)
return {
...authResult,
responseResult: generateSuccess('Password changed successfully')
operation: generateSuccess('Password changed successfully')
}
} catch (err) {
WIKI.logger.debug(err)
return generateError(err)
}
},
Expand All @@ -142,7 +144,7 @@ export default {
try {
await WIKI.db.users.loginForgotPassword(args, context)
return {
responseResult: generateSuccess('Password reset request processed.')
operation: generateSuccess('Password reset request processed.')
}
} catch (err) {
return generateError(err)
Expand All @@ -153,9 +155,11 @@ export default {
*/
async register (obj, args, context) {
try {
await WIKI.db.users.register({ ...args, verify: true }, context)
const usr = await WIKI.db.users.createNewUser({ ...args, userInitiated: true })
const authResult = await WIKI.db.users.afterLoginChecks(usr, WIKI.data.systemIds.localAuthId, context)
return {
responseResult: generateSuccess('Registration success')
...authResult,
operation: generateSuccess('Registration success')
}
} catch (err) {
return generateError(err)
Expand Down
36 changes: 19 additions & 17 deletions server/graph/schemas/authentication.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,16 @@ extend type Mutation {
username: String!
password: String!
strategyId: UUID!
siteId: UUID
): AuthenticationLoginResponse @rateLimit(limit: 5, duration: 60)
siteId: UUID!
): AuthenticationAuthResponse @rateLimit(limit: 5, duration: 60)

loginTFA(
continuationToken: String!
securityCode: String!
strategyId: UUID!
siteId: UUID!
setup: Boolean
): AuthenticationLoginResponse @rateLimit(limit: 5, duration: 60)
): AuthenticationAuthResponse @rateLimit(limit: 5, duration: 60)

changePassword(
userId: UUID
Expand All @@ -46,7 +48,7 @@ extend type Mutation {
newPassword: String!
strategyId: UUID!
siteId: UUID
): AuthenticationLoginResponse @rateLimit(limit: 5, duration: 60)
): AuthenticationAuthResponse @rateLimit(limit: 5, duration: 60)

forgotPassword(
email: String!
Expand All @@ -56,7 +58,7 @@ extend type Mutation {
email: String!
password: String!
name: String!
): AuthenticationRegisterResponse
): AuthenticationAuthResponse @rateLimit(limit: 5, duration: 60)

refreshToken(
token: String!
Expand Down Expand Up @@ -105,7 +107,7 @@ type AuthenticationActiveStrategy {
displayName: String
isEnabled: Boolean
config: JSON
selfRegistration: Boolean
registration: Boolean
allowedEmailRegex: String
autoEnrollGroups: [UUID]
}
Expand All @@ -116,22 +118,15 @@ type AuthenticationSiteStrategy {
isVisible: Boolean
}

type AuthenticationLoginResponse {
type AuthenticationAuthResponse {
operation: Operation
jwt: String
mustChangePwd: Boolean
mustProvideTFA: Boolean
mustSetupTFA: Boolean
nextAction: AuthenticationNextAction
continuationToken: String
redirect: String
tfaQRImage: String
}

type AuthenticationRegisterResponse {
operation: Operation
jwt: String
}

type AuthenticationTokenResponse {
operation: Operation
jwt: String
Expand All @@ -140,11 +135,11 @@ type AuthenticationTokenResponse {
input AuthenticationStrategyInput {
key: String!
strategyKey: String!
config: [KeyValuePairInput]
config: JSON!
displayName: String!
order: Int!
isEnabled: Boolean!
selfRegistration: Boolean!
registration: Boolean!
allowedEmailRegex: String!
autoEnrollGroups: [UUID]!
}
Expand All @@ -163,3 +158,10 @@ type AuthenticationCreateApiKeyResponse {
operation: Operation
key: String
}

enum AuthenticationNextAction {
changePassword
setupTfa
provideTfa
redirect
}
15 changes: 9 additions & 6 deletions server/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,13 @@
"admin.auth.configReferenceSubtitle": "Some strategies may require some configuration values to be set on your provider. These are provided for reference only and may not be needed by the current strategy.",
"admin.auth.displayName": "Display Name",
"admin.auth.displayNameHint": "The title shown to the end user for this authentication strategy.",
"admin.auth.emailValidation": "Email Validation",
"admin.auth.emailValidationHint": "Send a verification email to the user with a validation link when registering.",
"admin.auth.enabled": "Enabled",
"admin.auth.enabledForced": "This strategy cannot be disabled.",
"admin.auth.enabledHint": "Should this strategy be available to sites for login.",
"admin.auth.force2fa": "Force all users to use Two-Factor Authentication (2FA)",
"admin.auth.force2faHint": "Users will be required to setup 2FA the first time they login and cannot be disabled by the user.",
"admin.auth.enforceTfa": "Enforce Two-Factor Authentication",
"admin.auth.enforceTfaHint": "Users will be required to setup 2FA the first time they login and cannot be disabled by the user.",
"admin.auth.globalAdvSettings": "Global Advanced Settings",
"admin.auth.info": "Info",
"admin.auth.infoName": "Name",
Expand All @@ -90,10 +92,10 @@
"admin.auth.noConfigOption": "This strategy has no configuration options you can modify.",
"admin.auth.refreshSuccess": "List of strategies has been refreshed.",
"admin.auth.registration": "Registration",
"admin.auth.registrationHint": "Allow any user successfully authorized by the strategy to access the wiki.",
"admin.auth.registrationLocalHint": "Whether to allow guests to register new accounts.",
"admin.auth.saveSuccess": "Authentication configuration saved successfully.",
"admin.auth.security": "Security",
"admin.auth.selfRegistration": "Allow Self-Registration",
"admin.auth.selfRegistrationHint": "Allow any user successfully authorized by the strategy to access the wiki.",
"admin.auth.siteUrlNotSetup": "You must set a valid {siteUrl} first! Click on {general} in the left sidebar.",
"admin.auth.status": "Status",
"admin.auth.strategies": "Strategies",
Expand Down Expand Up @@ -1192,9 +1194,10 @@
"auth.tfa.subtitle": "Security code required:",
"auth.tfa.verifyToken": "Verify",
"auth.tfaFormTitle": "Enter the security code generated from your trusted device:",
"auth.tfaSetupInstrFirst": "1) Scan the QR code below from your mobile 2FA application:",
"auth.tfaSetupInstrSecond": "2) Enter the security code generated from your trusted device:",
"auth.tfaSetupInstrFirst": "Scan the QR code below from your mobile 2FA application:",
"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...",
"common.actions.activate": "Activate",
"common.actions.add": "Add",
"common.actions.apply": "Apply",
Expand Down
1 change: 1 addition & 0 deletions server/models/userKeys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export class UserKey extends Model {
}

static async generateToken ({ userId, kind, meta }, context) {
WIKI.logger.debug(`Generating ${kind} token for user ${userId}...`)
const token = await nanoid()
await WIKI.db.userKeys.query().insert({
kind,
Expand Down
Loading

0 comments on commit fe8066c

Please sign in to comment.