Add Korean Japanese and English user interfaces
This commit is contained in:
@@ -2,7 +2,7 @@ import { randomUUID, timingSafeEqual } from 'node:crypto';
|
||||
import { and, eq, gt, isNull, inArray } from 'drizzle-orm';
|
||||
import argon2 from 'argon2';
|
||||
import type { Service } from './service.js';
|
||||
import { PASSWORD_MIN_LENGTH, type Account } from '@hop/shared';
|
||||
import { PASSWORD_MIN_LENGTH, translate, type Account } from '@hop/shared';
|
||||
import * as s from './db/schema.js';
|
||||
import { fail, hash, randomToken } from './security.js';
|
||||
export type Mail = { to: string; subject: string; text: string; idempotencyKey?: string };
|
||||
@@ -88,7 +88,12 @@ export class Auth {
|
||||
fail(400, 'PASSWORD_INVALID', `비밀번호는 ${minimum}~1024자로 입력해 주세요.`);
|
||||
return argon2.hash(value, passwordOptions);
|
||||
}
|
||||
async token(user: User, purpose: 'verify' | 'reset' | 'email-change', value?: string) {
|
||||
async token(
|
||||
user: User,
|
||||
purpose: 'verify' | 'reset' | 'email-change',
|
||||
value?: string,
|
||||
language = 'ko',
|
||||
) {
|
||||
if (!this.mailReady)
|
||||
fail(503, 'MAIL_NOT_CONFIGURED', '이메일 기능이 아직 설정되지 않았습니다.');
|
||||
const token = randomToken(),
|
||||
@@ -127,8 +132,23 @@ export class Auth {
|
||||
await this.sendMail({
|
||||
idempotencyKey: `auth-${purpose}-${hash(token)}`,
|
||||
to: value ?? user.email!,
|
||||
subject: purpose === 'reset' ? 'hop. 비밀번호 재설정' : 'hop. 이메일 인증',
|
||||
text: `${purpose === 'reset' ? '비밀번호를 다시 설정하려면' : '이메일 인증을 완료하려면'} 다음 주소를 열어 주세요.\n${this.service.cfg.appUrl}/#${action}=${token}\n이 링크는 한 번만 사용할 수 있습니다.`,
|
||||
subject: translate(
|
||||
purpose === 'reset' ? 'hop. 비밀번호 재설정' : 'hop. 이메일 인증',
|
||||
language,
|
||||
),
|
||||
text: translate(
|
||||
'{p0} 다음 주소를 열어 주세요.\n{p1}/#{p2}={p3}\n이 링크는 한 번만 사용할 수 있습니다.',
|
||||
language,
|
||||
{
|
||||
p0: translate(
|
||||
purpose === 'reset' ? '비밀번호를 다시 설정하려면' : '이메일 인증을 완료하려면',
|
||||
language,
|
||||
),
|
||||
p1: this.service.cfg.appUrl,
|
||||
p2: action,
|
||||
p3: token,
|
||||
},
|
||||
),
|
||||
});
|
||||
}
|
||||
async register(
|
||||
@@ -137,6 +157,7 @@ export class Auth {
|
||||
displayName: string,
|
||||
ip: string,
|
||||
setupToken?: string,
|
||||
language = 'ko',
|
||||
) {
|
||||
const { db, cfg, limiter } = this.service;
|
||||
limiter.hit(`register-ip:${ip}`, 5, 3600000);
|
||||
@@ -174,7 +195,7 @@ export class Auth {
|
||||
.run();
|
||||
return this.user(id);
|
||||
});
|
||||
await this.token(user, 'verify');
|
||||
await this.token(user, 'verify', undefined, language);
|
||||
return { message: '가입을 진행할 수 있는 주소라면 인증 메일이 발송됩니다.' };
|
||||
}
|
||||
consume(
|
||||
@@ -324,7 +345,7 @@ export class Auth {
|
||||
}
|
||||
}
|
||||
}
|
||||
async resend(email: string, purpose: 'verify' | 'reset', ip: string) {
|
||||
async resend(email: string, purpose: 'verify' | 'reset', ip: string, language = 'ko') {
|
||||
const normalized = normalizeEmail(email);
|
||||
this.service.limiter.hit(`mail-ip:${ip}`, 10, 3600000);
|
||||
this.service.limiter.hit(`mail-email:${hash(normalized)}`, 3, 3600000);
|
||||
@@ -334,7 +355,7 @@ export class Auth {
|
||||
((purpose === 'verify' && user.status === 'pending') ||
|
||||
(purpose === 'reset' && ['active', 'pending'].includes(user.status)))
|
||||
)
|
||||
await this.token(user, purpose);
|
||||
await this.token(user, purpose, undefined, language);
|
||||
return { message: '처리 가능한 주소라면 안내 메일이 발송됩니다.' };
|
||||
}
|
||||
async resetPassword(token: string, password: string) {
|
||||
|
||||
Reference in New Issue
Block a user