Skip to content

Commit 985e1cf

Browse files
committed
feat: add password hash
1 parent 3975144 commit 985e1cf

5 files changed

Lines changed: 32 additions & 21 deletions

File tree

packages/core/src/auth/handlers/index.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@ import type { AuthConfig } from '..'
1010
export function createAuthHandlers<TAuthConfig extends AuthConfig>(config: TAuthConfig) {
1111
const handlers = {
1212
// No authentication required
13-
signUp: signUp({
14-
autoLogin: config.emailAndPassword?.signUp?.autoLogin ?? true,
15-
}),
16-
loginEmail: loginEmail({}),
13+
signUp: signUp(config),
14+
loginEmail: loginEmail(config),
1715
signOut: signOut({}),
1816
resetPasswordEmail: resetPasswordEmail({}),
1917
forgotPasswordEmail: forgotPasswordEmail({}),

packages/core/src/auth/handlers/login-email.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
import z from 'zod'
22

33
import { type ApiRouteHandler, type ApiRouteSchema, createEndpoint } from '../../endpoint'
4+
import type { AuthConfig } from '..'
45
import { AccountProvider } from '../constant'
56
import { type AuthContext } from '../context'
6-
import { setSessionCookie } from '../utils'
7+
import { setSessionCookie, verifyPassword } from '../utils'
78

8-
interface InternalRouteOptions {
9-
prefix?: string
10-
}
11-
12-
export function loginEmail<const TOptions extends InternalRouteOptions>(options: TOptions) {
9+
export function loginEmail<const TOptions extends AuthConfig>(options: TOptions) {
1310
const schema = {
1411
method: 'POST',
1512
path: '/api/auth/login-email',
@@ -36,9 +33,8 @@ export function loginEmail<const TOptions extends InternalRouteOptions>(options:
3633
AccountProvider.CREDENTIAL
3734
)
3835

39-
// TODO: Hash password and compare
40-
const hashPassword = account.password
41-
if (account.password !== hashPassword) {
36+
const verifyStatus = await verifyPassword(args.body.password, account.password as string)
37+
if (!verifyStatus) {
4238
throw new Error('Invalid password')
4339
}
4440

packages/core/src/auth/handlers/sign-up.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
import z from 'zod'
22

33
import { type ApiRouteHandler, type ApiRouteSchema, createEndpoint } from '../../endpoint'
4+
import type { AuthConfig } from '..'
45
import { AccountProvider } from '../constant'
56
import { type AuthContext } from '../context'
6-
import { setSessionCookie } from '../utils'
7+
import { hashPassword, setSessionCookie } from '../utils'
78

8-
interface InternalRouteOptions {
9-
autoLogin?: boolean
10-
}
11-
12-
export function signUp<const TOptions extends InternalRouteOptions>(options: TOptions) {
9+
export function signUp<const TOptions extends AuthConfig>(options: TOptions) {
1310
const schema = {
1411
method: 'POST',
1512
path: '/api/auth/sign-up',
@@ -34,7 +31,9 @@ export function signUp<const TOptions extends InternalRouteOptions>(options: TOp
3431
} as const satisfies ApiRouteSchema
3532

3633
const handler: ApiRouteHandler<AuthContext, typeof schema> = async (args) => {
37-
const hasedPassword = args.body.password // TODO: hash password
34+
const hasedPassword =
35+
(await options.emailAndPassword?.passwordHasher?.(args.body.password)) ??
36+
(await hashPassword(args.body.password))
3837

3938
const user = await args.context.internalHandlers.user.create({
4039
name: args.body.name,
@@ -60,7 +59,7 @@ export function signUp<const TOptions extends InternalRouteOptions>(options: TOp
6059
})
6160

6261
const responseHeaders = {}
63-
if (options.autoLogin) {
62+
if (options.emailAndPassword?.signUp?.autoLogin !== false) {
6463
// Set session cookie if auto login is enabled
6564
setSessionCookie(responseHeaders, session.token)
6665
}

packages/core/src/auth/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export interface AuthConfig {
7373
}
7474
emailAndPassword?: {
7575
enabled: boolean
76+
passwordHasher?: (password: string) => Promise<string> // default: bcrypt
7677
signUp?: {
7778
autoLogin?: boolean // default: true
7879
additionalFields?: Fields<any>

packages/core/src/auth/utils.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import { parse as parseCookies, serialize } from 'cookie-es'
2+
import crypto, { randomBytes } from 'crypto'
3+
import { promisify } from 'util'
4+
5+
const scrypt = promisify(crypto.scrypt)
26

37
function setCookie(
48
headers: Record<string, string> | undefined,
@@ -30,3 +34,16 @@ export function setSessionCookie(headers: Record<string, string> | undefined, va
3034
export function deleteSessionCookie(headers?: Record<string, string>) {
3135
deleteCookie(headers, SESSION_COOKIE_NAME)
3236
}
37+
38+
export async function hashPassword(password: string): Promise<string> {
39+
const salt = randomBytes(8).toString('hex')
40+
const derivedKey = await scrypt(password, salt, 64)
41+
return salt + ':' + (derivedKey as Buffer).toString('hex')
42+
}
43+
44+
export async function verifyPassword(password: string, hashedPassword: string): Promise<boolean> {
45+
const [salt, key] = hashedPassword.split(':')
46+
const keyBuffer = Buffer.from(key, 'hex')
47+
const derivedKey = await scrypt(password, salt, 64)
48+
return crypto.timingSafeEqual(keyBuffer, derivedKey as any)
49+
}

0 commit comments

Comments
 (0)