@youngshand/payload-auth-plugin
Version:
A temporary fork for testing of Authentication plugin for Payload CMS, use @payload-auth-plugin
355 lines (313 loc) • 7.81 kB
text/typescript
import { getCookieExpiration, parseCookies, PayloadRequest } from "payload"
import {
AuthenticationFailed,
EmailAlreadyExistError,
InvalidCredentials,
InvalidRequestBodyError,
UnauthorizedAPIRequest,
UserNotFoundAPIError,
} from "../errors/apiErrors.js"
import { hashPassword, verifyPassword } from "../utils/password.js"
import { SuccessKind } from "../../types.js"
import { ephemeralCode, verifyEphemeralCode } from "../utils/hash.js"
import { EPHEMERAL_CODE_COOKIE_NAME } from "../../constants.js"
import {
createSessionCookies,
invalidateSessionCookies,
verifySessionCookie,
} from "../utils/cookies.js"
import { revokeSession } from "../utils/session.js"
export const PasswordSignin = async (
request: PayloadRequest,
internal: {
usersCollectionSlug: string
},
sessionCallBack: (user: { id: string; email: string }) => Promise<Response>,
) => {
const body =
request.json &&
((await request.json()) as { email: string; password: string })
if (!body?.email || !body.password) {
return new InvalidRequestBodyError()
}
const { payload } = request
const { docs } = await payload.find({
collection: internal.usersCollectionSlug,
where: {
email: { equals: body.email },
},
limit: 1,
})
if (docs.length !== 1) {
return new UserNotFoundAPIError()
}
const user = docs[0]
const isVerifed = await verifyPassword(
body.password,
user["hashedPassword"],
user["salt"],
user["hashIterations"],
)
if (!isVerifed) {
return new InvalidCredentials()
}
return sessionCallBack({
id: user.id as string,
email: body.email,
})
}
export const PasswordSignup = async (
request: PayloadRequest,
internal: {
usersCollectionSlug: string
},
sessionCallBack: (user: { id: string; email: string }) => Promise<Response>,
) => {
const body =
request.json &&
((await request.json()) as {
email: string
password: string
allowAutoSignin?: boolean
profile?: Record<string, unknown>
})
if (!body?.email || !body.password) {
return new InvalidRequestBodyError()
}
const { payload } = request
const { docs } = await payload.find({
collection: internal.usersCollectionSlug,
where: {
email: { equals: body.email },
},
limit: 1,
})
if (docs.length > 0) {
return new EmailAlreadyExistError()
}
const {
hash: hashedPassword,
salt,
iterations,
} = await hashPassword(body.password)
const user = await payload.create({
collection: internal.usersCollectionSlug,
data: {
email: body.email,
hashedPassword: hashedPassword,
hashIterations: iterations,
salt,
...body.profile,
},
})
if (body.allowAutoSignin) {
return sessionCallBack({
id: user.id as string,
email: body.email,
})
}
return Response.json(
{
message: "Signed up successfully",
kind: SuccessKind.Created,
isSuccess: true,
isError: false,
},
{ status: 201 },
)
}
export const ForgotPasswordInit = async (
request: PayloadRequest,
internal: {
usersCollectionSlug: string
},
) => {
const { payload } = request
const body =
request.json &&
((await request.json()) as {
email: string
})
if (!body?.email) {
return new InvalidRequestBodyError()
}
const { docs } = await payload.find({
collection: internal.usersCollectionSlug,
where: {
email: { equals: body.email },
},
limit: 1,
})
if (docs.length !== 1) {
return new UserNotFoundAPIError()
}
const { code, hash } = await ephemeralCode(6, payload.secret)
await payload.sendEmail({
to: body.email,
subject: "Password recovery",
text: "Password recovery code: " + code,
})
const res = new Response(
JSON.stringify({
message: "Password recovery initiated successfully",
kind: SuccessKind.Created,
isSuccess: true,
isError: false,
}),
{ status: 201 },
)
const tokenExpiration = getCookieExpiration({
seconds: 300,
})
res.headers.append(
"Set-Cookie",
`${EPHEMERAL_CODE_COOKIE_NAME}=${hash};Path=/;HttpOnly;Secure=true;SameSite=lax;Expires=${tokenExpiration.toUTCString()}`,
)
return res
}
export const ForgotPasswordVerify = async (
request: PayloadRequest,
internal: {
usersCollectionSlug: string
},
) => {
const { payload } = request
const body =
request.json &&
((await request.json()) as {
email: string
password: string
code: string
})
if (!body?.email || !body?.password || !body.code) {
return new InvalidRequestBodyError()
}
const cookies = parseCookies(request.headers)
const hash = cookies.get(EPHEMERAL_CODE_COOKIE_NAME)
if (!hash) {
return new UnauthorizedAPIRequest()
}
const isVerified = await verifyEphemeralCode(body.code, hash, payload.secret)
if (!isVerified) {
return new AuthenticationFailed()
}
const { docs } = await payload.find({
collection: internal.usersCollectionSlug,
where: {
email: { equals: body.email },
},
limit: 1,
})
if (docs.length !== 1) {
return new UserNotFoundAPIError()
}
const {
hash: hashedPassword,
salt,
iterations,
} = await hashPassword(body.password)
await payload.update({
collection: internal.usersCollectionSlug,
id: docs[0].id,
data: {
hashedPassword,
salt,
hashIterations: iterations,
},
})
const res = new Response(
JSON.stringify({
message: "Password recovered successfully",
kind: SuccessKind.Updated,
isSuccess: true,
isError: false,
}),
{ status: 201 },
)
res.headers.append(
"Set-Cookie",
`${EPHEMERAL_CODE_COOKIE_NAME}=;Path=/;HttpOnly;Secure=true;SameSite=lax;Expires=Thu, 01 Jan 1970 00:00:00 GMT`,
)
return res
}
export const ResetPassword = async (
cookieName: string,
secret: string,
internal: {
usersCollectionSlug: string
},
request: PayloadRequest,
) => {
const { payload } = request
const cookies = parseCookies(request.headers)
const token = cookies.get(cookieName)
if (!token) {
return new UnauthorizedAPIRequest()
}
const jwtResponse = await verifySessionCookie(token, secret)
if (!jwtResponse.payload) {
return new UnauthorizedAPIRequest()
}
const body =
request.json &&
((await request.json()) as {
email: string
currentPassword: string
newPassword: string
signoutOnUpdate?: boolean | undefined
})
if (!body?.email || !body?.currentPassword || !body?.newPassword) {
return new InvalidRequestBodyError()
}
const { docs } = await payload.find({
collection: internal.usersCollectionSlug,
where: {
email: { equals: body.email },
},
limit: 1,
})
if (docs.length !== 1) {
return new UserNotFoundAPIError()
}
const user = docs[0]
const isVerifed = await verifyPassword(
body.currentPassword,
user["hashedPassword"],
user["salt"],
user["hashIterations"],
)
if (!isVerifed) {
return new InvalidCredentials()
}
const {
hash: hashedPassword,
salt,
iterations,
} = await hashPassword(body.newPassword)
await payload.update({
collection: internal.usersCollectionSlug,
id: user.id,
data: {
hashedPassword,
salt,
hashIterations: iterations,
},
})
if (!!body.signoutOnUpdate) {
let cookies: string[] = []
cookies = [...invalidateSessionCookies(cookieName, cookies)]
return revokeSession(cookies)
}
const res = new Response(
JSON.stringify({
message: "Password reset complete",
kind: SuccessKind.Updated,
isSuccess: true,
isError: false,
}),
{
status: 201,
},
)
return res
}