authenzify
Version:
server to manage authentication authorization of users and more
409 lines (355 loc) • 11.3 kB
JavaScript
import httpStatus from 'http-status'
import { HttpError } from 'core-services-sdk'
import { cleanUser } from '../util/helpers.js'
import { SIGN_UP_SUCCEEDED } from '../api/responses.js'
import { SIGN_IN_ERRORS, USER_ACTIONS } from '../errors/error-codes.js'
import { withErrorHandlingReply } from '../errors/with-error-reply-handling.js'
export const initUsersRoutes = ({
configService,
usersManagement,
tryToExtractUserAuthenticated,
}) => {
const replyToken = ({ token, reply }, payload = {}) => {
if (!configService.setCookieOnSignIn) {
reply.send({ ...payload, token })
return
}
reply
.clearCookie(configService.authorizationCookieKey)
.setCookie(configService.authorizationCookieKey, token, {
path: '/',
signed: false,
sameSite: true,
})
.send({ ...payload, token })
}
const validateAuthentication = async (request, reply) => {
try {
const userInfo = await tryToExtractUserAuthenticated(request)
request.requestContext.set('userInfo', userInfo)
} catch (error) {
reply
.status(httpStatus.UNAUTHORIZED)
.send({ message: httpStatus[httpStatus.UNAUTHORIZED] })
return reply
}
}
return async (webServer) => {
webServer.addHook('onRequest', async (request, reply) => {
const correlationId = request.id
request.log = request.log.child({
correlationId,
userAgent: request.headers['user-agent'] || 'unknown',
})
request.correlationId = correlationId
reply.header('x-correlation-id', correlationId)
})
await webServer.post('/sign-up', function (request, reply) {
withErrorHandlingReply({
reply,
log: this.log,
})(async () => {
const { body } = request
const userDetails = body
this.log.info(`Sign up user email: '${userDetails.email}'`)
const user = await usersManagement.signUp(userDetails)
this.log.info(
`Succeeded to create user: { email: ${user.email}, id: ${user.id} }`,
)
reply
.status(SIGN_UP_SUCCEEDED.httpStatusCode)
.send({ ...SIGN_UP_SUCCEEDED.httpResponse })
})
})
await webServer.post('/sign-in', function (request, reply) {
withErrorHandlingReply({
reply,
log: this.log,
})(async () => {
const { body } = request
const userDetails = body
this.log.info(`Sign in user email: '${userDetails.email}'`)
const token = await usersManagement.signIn(userDetails)
replyToken({ token, reply })
})
})
await webServer.get(
'/:id',
{
preHandler: [validateAuthentication],
},
function (request, reply) {
withErrorHandlingReply({
reply,
log: this.log,
})(async () => {
const params = request.params
const { id } = params
this.log.info(`Get user by id: '${id}'`)
const user = await usersManagement.getUser({ id })
if (!user) {
throw new HttpError(SIGN_IN_ERRORS.USER_NOT_EXIST)
}
reply.send(cleanUser(user))
})
},
)
await webServer.get(
'/me',
{ preHandler: [validateAuthentication] },
function (request, reply) {
withErrorHandlingReply({
reply,
log: this.log,
})(async () => {
const { id } = request.requestContext.get('userInfo')
this.log.info(`Get user 'me' by id: '${id}'`)
const user = await usersManagement.getUser({ id })
if (!user) {
throw new HttpError(SIGN_IN_ERRORS.USER_NOT_EXIST)
}
reply.send(cleanUser(user))
})
},
)
await webServer.get('/verify/:id/activation', function (request, reply) {
withErrorHandlingReply({
reply,
log: this.log,
})(async () => {
const params = request.params
const { id } = params
this.log.info(`Verify user by activation id: '${id}'`)
const verified = await usersManagement.verifyActivation(id)
reply.send({ verified })
})
})
await webServer.post(
'/:id/permissions',
{ preHandler: [validateAuthentication] },
function (request, reply) {
withErrorHandlingReply({
reply,
log: this.log,
})(async () => {
const { id } = request.params
const userInfo = request.requestContext.get('userInfo')
const { body: companyDetails } = request
const { isAdminUser, token } =
await usersManagement.requestPermissionForUser({
companyDetails,
userInfo,
id,
})
if (isAdminUser) {
replyToken({ token, reply }, { isAdminUser, emailSent: false })
return
}
reply.send({ emailSent: true, isAdminUser })
})
},
)
await webServer.get(
'/verify/:id/permissions/:role',
{ preHandler: [validateAuthentication] },
function (request, reply) {
withErrorHandlingReply({
reply,
log: this.log,
})(async () => {
const { id, role } = request.params
const userInfo = request.requestContext.get('userInfo')
const verified = await usersManagement.verifyUserPermissionRequest({
verificationId: id,
role,
userInfo,
})
reply.send({ verified })
})
},
)
await webServer.post(
'/change-password',
{ preHandler: [validateAuthentication] },
function (request, reply) {
withErrorHandlingReply({
reply,
log: this.log,
})(async () => {
const userInfo = request.requestContext.get('userInfo')
const {
body: { password, newPassword },
} = request
const passwordChanged = await usersManagement.changePassword({
password,
newPassword,
userInfo,
})
reply.send({ passwordChanged })
})
},
)
await webServer.post('/forgot-password', function (request, reply) {
withErrorHandlingReply({
reply,
log: this.log,
})(async () => {
const {
body: { email },
log,
} = request
try {
const passwordChanged = await usersManagement.forgotPassword({
email,
})
reply.send({ passwordChanged })
} catch (error) {
log.error(error)
reply.send({ passwordChanged: true })
}
})
})
await webServer.post(
'/verify/:verificationId/apply-forgot-password',
function (request, reply) {
withErrorHandlingReply({
reply,
log: this.log,
})(async () => {
const {
params,
body: { newPassword },
log,
} = request
const { verificationId } = params
try {
const passwordChanged = await usersManagement.applyForgotPassword({
newPassword,
verificationId,
})
reply.send({ passwordChanged })
} catch (error) {
log.error(error)
reply.send({ passwordChanged: true })
}
})
},
)
await webServer.post('/sign-in-google', function (request, reply) {
withErrorHandlingReply({
reply,
log: this.log,
})(async () => {
const { body } = request
this.log.info(`Sign in user email: '${'userDetails.email'}'`)
const token = await usersManagement.signInGoogle(body)
replyToken({ token, reply })
})
})
await webServer.post('/sign-in/code', function (request, reply) {
withErrorHandlingReply({
reply,
log: this.log,
})(async () => {
const { signInVia, identifier, deviceIdentity } = request.body
const verification = await usersManagement.getSignInCode({
signInVia,
identifier,
deviceIdentity,
})
reply.send(verification)
})
})
await webServer.post('/sign-in/verify', function (request, reply) {
withErrorHandlingReply({
reply,
log: this.log,
})(async () => {
const { identifier, code, signedInVia, verificationId } = request.body
const token = await usersManagement.signInOrUpWithCode({
code,
identifier,
signedInVia,
verificationId,
})
this.log.info(`User verified and signed in: ${identifier}`)
replyToken({ token, reply })
})
})
await webServer.post(
'/assign-phone-or-email/code',
{ preHandler: [validateAuthentication] },
function (request, reply) {
withErrorHandlingReply({
reply,
log: this.log,
})(async () => {
const { id } = request.requestContext.get('userInfo')
this.log.info(`Get user 'me' by id: '${id}'`)
const user = await usersManagement.getUser({ id })
if (!user) {
throw new HttpError(SIGN_IN_ERRORS.USER_NOT_EXIST)
}
const { identifier, assignType } = request.body
const verification = await usersManagement.getAssignPhoneOrEmailCode({
user,
assignInfo: { identifier, assignType },
})
reply.send(verification)
})
},
)
await webServer.post(
'/assign-phone-or-email/verify',
{ preHandler: [validateAuthentication] },
function (request, reply) {
withErrorHandlingReply({
reply,
log: this.log,
})(async () => {
const { id } = request.requestContext.get('userInfo')
this.log.info(`Get user 'me' by id: '${id}'`)
const user = await usersManagement.getUser({ id })
if (!user) {
throw new HttpError(SIGN_IN_ERRORS.USER_NOT_EXIST)
}
const { code, identifier, assignType, verificationId } = request.body
const verification = await usersManagement.assignIdentifierViaCode({
user,
code,
identifier,
assignType,
verificationId,
})
reply.send(verification)
})
},
)
await webServer.put(
'/:userId/profile',
{ preHandler: [validateAuthentication] },
function (request, reply) {
withErrorHandlingReply({
reply,
log: this.log,
})(async () => {
const user = request.requestContext.get('userInfo')
const { id } = user
this.log.info(`Update user's profile '${id}'`)
const { body: profile, params } = request
const { userId } = params
if (userId !== id) {
throw new HttpError(
USER_ACTIONS.ONLY_USER_ACCOUNT_CAN_CHANGE_IS_PROFILE,
)
}
const userUpdated = await usersManagement.updateUserProfile({
user,
profile,
})
reply.send(cleanUser(userUpdated))
})
},
)
}
}