feat: 레거시 재이관과 계정 복구 기반을 추가

중앙 이전 기록 스키마와 버전 정규화기를 도입하고, 재실행 시 현행 계정 상태를 보존한다. 카카오 인증 뒤 이관 비밀번호를 1회 설정하는 흐름과 비카카오 계정용 안전한 CLI 복구 경로를 추가한다.
This commit is contained in:
2026-08-17 15:55:32 +00:00
parent 50e7d894e4
commit fc7de05017
33 changed files with 1720 additions and 150 deletions
+11 -4
View File
@@ -14,7 +14,7 @@ export interface OAuthPendingState {
export interface OAuthSession {
id: string;
mode: OAuthMode;
intent?: 'register' | 'link_existing' | 'rejoin';
intent?: 'register' | 'link_existing' | 'rejoin' | 'password_setup';
targetUserId?: string;
kakaoId: string;
email: string;
@@ -93,6 +93,14 @@ end
return cjson.encode({ status = 'verified', userId = challenge.userId })
`;
const consumeOnceScript = `
local raw = redis.call('GET', KEYS[1])
if raw then
redis.call('DEL', KEYS[1])
end
return raw
`;
export class RedisOAuthSessionStore implements OAuthSessionStore {
private readonly client: RedisClientLike;
private readonly prefix: string;
@@ -161,12 +169,11 @@ export class RedisOAuthSessionStore implements OAuthSessionStore {
async consumeSession(sessionId: string): Promise<OAuthSession | null> {
const key = this.sessionKey(sessionId);
const raw = await this.client.get(key);
const raw = await this.client.eval(consumeOnceScript, { keys: [key], arguments: [] });
if (!raw) {
return null;
}
await this.client.del(key);
return parseJson<OAuthSession>(raw);
return typeof raw === 'string' ? parseJson<OAuthSession>(raw) : null;
}
async getLoginChallengeForUser(userId: string): Promise<KakaoLoginChallenge | null> {