register.js 1.74 KB
/**
 * 注册流程:密码经 hasher 哈希落库 + 首次签发会话。
 *
 * 产品决策(§3 留口):本实现采用「注册即激活并签发会话」路径(欧美刷剧 MVP 追求转化、
 * 降低试看→注册摩擦)。验证码确认走独立 sendCode/verifyCode(用于敏感操作 / reset),
 * 不阻塞注册主路径。库内 grep 不到明文密码持久化、不到自写哈希循环(全走 hasher)。
 */

import { AuthError } from './errors.js'
import { issueSession } from './session.js'

/**
 * @param {Object} deps
 * @param {import('./index.js').UserRepo} deps.userRepo
 * @param {import('./index.js').TokenSigner} deps.tokenSigner
 * @param {import('./index.js').Hasher} deps.hasher
 * @param {number} [deps.accessTtlMs]
 * @param {number} [deps.refreshTtlMs]
 * @returns {(args: { email: string, password: string, locale?: string, email_verified_at?: Date }) => Promise<import('./index.js').AuthTokens>}
 */
export function makeRegister(deps) {
  const { userRepo, hasher } = deps
  return async function register({ email, password, locale, email_verified_at }) {
    const normalized = String(email ?? '').trim().toLowerCase()
    if (!normalized || !password) {
      throw new AuthError({ code: 'INVALID_CREDENTIALS', message: 'email and password required' })
    }

    const existing = await userRepo.findByEmail(normalized)
    if (existing) {
      throw new AuthError({ code: 'EMAIL_TAKEN' })
    }

    const password_hash = await hasher.hash(password)
    const user = await userRepo.create({ email: normalized, password_hash, locale, email_verified_at })

    const issued = await issueSession(deps, { user_id: user.id })
    return { user, access_token: issued.access_token, refresh_token: issued.refresh_token }
  }
}