mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 13:00:39 +00:00
feat: refactor authentication flow and improve token verification process
This commit is contained in:
+1
-2
@@ -3,7 +3,7 @@ import { NotificationsHub } from './durable/notifications-hub';
|
||||
import { handleRequest } from './router';
|
||||
import { StorageService } from './services/storage';
|
||||
import { applyCors, jsonResponse } from './utils/response';
|
||||
import { runScheduledBackupIfDue, seedDefaultBackupSettings } from './handlers/backup';
|
||||
import { runScheduledBackupIfDue } from './handlers/backup';
|
||||
import { buildWebBootstrapResponse } from './router-public';
|
||||
|
||||
let dbInitialized = false;
|
||||
@@ -59,7 +59,6 @@ async function ensureDatabaseInitialized(env: Env): Promise<void> {
|
||||
dbInitPromise = (async () => {
|
||||
const storage = new StorageService(env.DB);
|
||||
await storage.initializeDatabase();
|
||||
await seedDefaultBackupSettings(env);
|
||||
dbInitialized = true;
|
||||
dbInitError = null;
|
||||
})()
|
||||
|
||||
+3
-8
@@ -1,6 +1,5 @@
|
||||
import { DEFAULT_DEV_SECRET, Env } from './types';
|
||||
import { AuthService } from './services/auth';
|
||||
import { StorageService } from './services/storage';
|
||||
import { RateLimitService, getClientIdentifier } from './services/ratelimit';
|
||||
import { handleCors, errorResponse } from './utils/response';
|
||||
import { LIMITS } from './config/limits';
|
||||
@@ -96,10 +95,11 @@ export async function handleRequest(request: Request, env: Env): Promise<Respons
|
||||
|
||||
const auth = new AuthService(env);
|
||||
const authHeader = request.headers.get('Authorization');
|
||||
const payload = await auth.verifyAccessToken(authHeader);
|
||||
if (!payload) {
|
||||
const verified = await auth.verifyAccessTokenWithUser(authHeader);
|
||||
if (!verified) {
|
||||
return errorResponse('Unauthorized', 401);
|
||||
}
|
||||
const { payload, user: currentUser } = verified;
|
||||
|
||||
const actingDeviceId = String(payload.did || '').trim();
|
||||
if (actingDeviceId) {
|
||||
@@ -109,11 +109,6 @@ export async function handleRequest(request: Request, env: Env): Promise<Respons
|
||||
}
|
||||
|
||||
const userId = payload.sub;
|
||||
const storage = new StorageService(env.DB);
|
||||
const currentUser = await storage.getUserById(userId);
|
||||
if (!currentUser) {
|
||||
return errorResponse('Unauthorized', 401);
|
||||
}
|
||||
if (currentUser.status !== 'active') {
|
||||
return errorResponse('Account is disabled', 403);
|
||||
}
|
||||
|
||||
+14
-5
@@ -7,6 +7,11 @@ import { StorageService } from './storage';
|
||||
// This second layer only needs to be non-trivial, not expensive.
|
||||
const SERVER_HASH_ITERATIONS = 100_000;
|
||||
|
||||
export interface VerifiedAccessContext {
|
||||
payload: JWTPayload;
|
||||
user: User;
|
||||
}
|
||||
|
||||
export class AuthService {
|
||||
private storage: StorageService;
|
||||
|
||||
@@ -81,8 +86,7 @@ export class AuthService {
|
||||
return token;
|
||||
}
|
||||
|
||||
// Verify access token from Authorization header
|
||||
async verifyAccessToken(authHeader: string | null): Promise<JWTPayload | null> {
|
||||
async verifyAccessTokenWithUser(authHeader: string | null): Promise<VerifiedAccessContext | null> {
|
||||
if (!authHeader) return null;
|
||||
|
||||
const parts = authHeader.split(' ');
|
||||
@@ -93,12 +97,11 @@ export class AuthService {
|
||||
const payload = await verifyJWT(parts[1], this.env.JWT_SECRET);
|
||||
if (!payload) return null;
|
||||
|
||||
// Verify security stamp - ensures token is invalidated after password change
|
||||
const user = await this.storage.getUserById(payload.sub);
|
||||
if (!user) return null;
|
||||
|
||||
if (payload.sstamp !== user.securityStamp) {
|
||||
return null; // Token was issued before password change
|
||||
return null;
|
||||
}
|
||||
|
||||
if (payload.did) {
|
||||
@@ -107,7 +110,13 @@ export class AuthService {
|
||||
if (!payload.dstamp || payload.dstamp !== device.sessionStamp) return null;
|
||||
}
|
||||
|
||||
return payload;
|
||||
return { payload, user };
|
||||
}
|
||||
|
||||
// Verify access token from Authorization header
|
||||
async verifyAccessToken(authHeader: string | null): Promise<JWTPayload | null> {
|
||||
const verified = await this.verifyAccessTokenWithUser(authHeader);
|
||||
return verified?.payload ?? null;
|
||||
}
|
||||
|
||||
// Refresh access token
|
||||
|
||||
@@ -101,6 +101,8 @@ import {
|
||||
} from './storage-revision-repo';
|
||||
|
||||
const TWO_FACTOR_REMEMBER_TTL_MS = 30 * 24 * 60 * 60 * 1000;
|
||||
const STORAGE_SCHEMA_VERSION_KEY = 'schema.version';
|
||||
const STORAGE_SCHEMA_VERSION = '2026-03-18.1';
|
||||
|
||||
// D1-backed storage.
|
||||
// Contract:
|
||||
@@ -171,7 +173,13 @@ export class StorageService {
|
||||
// - Keep statements idempotent so updates are safe.
|
||||
async initializeDatabase(): Promise<void> {
|
||||
if (StorageService.schemaVerified) return;
|
||||
await ensureStorageSchema(this.db);
|
||||
|
||||
await this.db.prepare('CREATE TABLE IF NOT EXISTS config (key TEXT PRIMARY KEY, value TEXT NOT NULL)').run();
|
||||
const schemaVersion = await getStoredConfigValue(this.db, STORAGE_SCHEMA_VERSION_KEY);
|
||||
if (schemaVersion !== STORAGE_SCHEMA_VERSION) {
|
||||
await ensureStorageSchema(this.db);
|
||||
await saveConfigValue(this.db, STORAGE_SCHEMA_VERSION_KEY, STORAGE_SCHEMA_VERSION);
|
||||
}
|
||||
|
||||
StorageService.schemaVerified = true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user