feat: First-Start Create Admin Modal

This commit is contained in:
OpenVelo Agent
2026-07-21 14:45:53 +00:00
parent 81fd3d8a29
commit 8642669952
22 changed files with 1009 additions and 130 deletions
+60 -5
View File
@@ -136,11 +136,31 @@ export class AuthService implements OnModuleInit {
* token so the caller can set the HttpOnly cookie.
*/
private async mintSession(manager: EntityManager, user: UserEntity): Promise<LoginResponse> {
const accessToken = await this.signAccess(user);
const refreshToken = this.generateRefreshToken();
const tokenHash = this.hashToken(refreshToken);
return AuthService.mintSessionWith(
manager,
this.jwt,
this.config,
this.refreshTtlSeconds,
user,
);
}
/**
* Stateless mint helper used by other modules (e.g. setup) that need to
* create a session without depending on the full AuthModule injection tree.
*/
static async mintSessionWith(
manager: EntityManager,
jwt: JwtService,
config: ConfigService,
refreshTtlSeconds: number,
user: UserEntity,
): Promise<LoginResponse> {
const accessToken = await jwt.signAsync({ sub: user.id, username: user.username, role: user.role });
const refreshToken = AuthService.generateRefreshTokenStatic();
const tokenHash = AuthService.hashTokenStatic(refreshToken);
const now = new Date();
const expires = new Date(now.getTime() + this.refreshTtlSeconds * 1000);
const expires = new Date(now.getTime() + refreshTtlSeconds * 1000);
await manager.insert(RefreshTokenEntity, {
id: uuid(),
userId: user.id,
@@ -152,11 +172,46 @@ export class AuthService implements OnModuleInit {
return {
accessToken,
refreshToken,
expiresIn: this.accessTtlSeconds(),
expiresIn: AuthService.accessTtlSecondsStatic(config),
user: { id: user.id, username: user.username, role: user.role },
};
}
private static generateRefreshTokenStatic(): string {
return crypto.randomBytes(32).toString('base64url');
}
private static hashTokenStatic(token: string): string {
return crypto.createHash('sha256').update(token).digest('hex');
}
private static accessTtlSecondsStatic(config: ConfigService): number {
const ttl = config.get<string>('JWT_ACCESS_TTL', '15m');
return AuthService.parseTtlStatic(ttl);
}
private static parseTtlStatic(ttl: string): number {
const m = /^(\d+)([smhd])$/.exec(ttl.trim());
if (!m) return 900;
const n = parseInt(m[1], 10);
const unit = m[2];
const mult = unit === 's' ? 1 : unit === 'm' ? 60 : unit === 'h' ? 3600 : 86400;
return n * mult;
}
/**
* Public access to the static session mint for cross-module reuse.
* The instance-based `mintSession(manager, user)` is preserved for
* existing callers and delegates here.
*/
static async createSession(
manager: EntityManager,
deps: { jwt: JwtService; config: ConfigService; refreshTtlSeconds: number },
user: UserEntity,
): Promise<LoginResponse> {
return AuthService.mintSessionWith(manager, deps.jwt, deps.config, deps.refreshTtlSeconds, user);
}
private async signAccess(user: UserEntity): Promise<string> {
return this.jwt.signAsync({ sub: user.id, username: user.username, role: user.role });
}