Add utils package with Web Crypto password hashing
- Create @reviq/utils package with PBKDF2-SHA256 password hashing compatible with Cloudflare Workers (uses crypto.subtle) - Update api-server and CLI to use new utils package for consistent password hashing format across the codebase - Add pino logging to api-server for better request debugging - Make login request tokens cryptographically secure base58 strings instead of database IDs - Add migration to make login_requests.token non-nullable with unique constraint - Fix RPCLink URL construction for client-side API calls - Add db:codegen script to root package.json Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,9 +3,9 @@
|
||||
* Public procedure - no authentication required
|
||||
*
|
||||
* Flow:
|
||||
* 1. Read rev.login_request_token cookie (could be real ID or fake UUID)
|
||||
* 2. If fake token (not found in DB): return { status: 'pending' }
|
||||
* 3. If valid login request ID:
|
||||
* 1. Read rev.login_request_token cookie
|
||||
* 2. If token not found in DB (fake or expired): return { status: 'pending' }
|
||||
* 3. If valid login request:
|
||||
* - Check if expired: return { status: 'expired' }
|
||||
* - Check if not completed: return { status: 'pending' }
|
||||
* - If completed:
|
||||
@@ -31,15 +31,6 @@ import {
|
||||
} from "../../utils/session.js";
|
||||
import { os } from "../base.js";
|
||||
|
||||
/**
|
||||
* Check if a string looks like a UUID (fake token)
|
||||
*/
|
||||
const isUUID = (str: string): boolean => {
|
||||
const uuidRegex =
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||
return uuidRegex.test(str);
|
||||
};
|
||||
|
||||
/**
|
||||
* Login if request is completed handler
|
||||
* Polls for login completion and creates session when ready
|
||||
@@ -57,21 +48,7 @@ export const loginIfRequestIsCompleted =
|
||||
return { status: "pending" as const };
|
||||
}
|
||||
|
||||
// Check if it's a fake token (UUID)
|
||||
if (isUUID(loginRequestToken)) {
|
||||
// Fake token - user doesn't exist
|
||||
// The cookie will expire naturally after 15 minutes
|
||||
return { status: "pending" as const };
|
||||
}
|
||||
|
||||
// Try to parse as login request ID
|
||||
const loginRequestId = Number.parseInt(loginRequestToken, 10);
|
||||
if (Number.isNaN(loginRequestId)) {
|
||||
// Invalid format - treat as pending
|
||||
return { status: "pending" as const };
|
||||
}
|
||||
|
||||
// Fetch login request from database
|
||||
// Fetch login request from database by token
|
||||
const loginRequest = await context.db
|
||||
.selectFrom("login_requests")
|
||||
.select([
|
||||
@@ -81,7 +58,7 @@ export const loginIfRequestIsCompleted =
|
||||
"completed_at",
|
||||
"expires_at",
|
||||
])
|
||||
.where("id", "=", String(loginRequestId))
|
||||
.where("token", "=", loginRequestToken)
|
||||
.executeTakeFirst();
|
||||
|
||||
// Login request not found - might have been deleted or invalid ID
|
||||
@@ -140,7 +117,7 @@ export const loginIfRequestIsCompleted =
|
||||
// Delete the login request (it's been consumed)
|
||||
await context.db
|
||||
.deleteFrom("login_requests")
|
||||
.where("id", "=", String(loginRequestId))
|
||||
.where("id", "=", loginRequest.id)
|
||||
.execute();
|
||||
|
||||
// Set session cookie
|
||||
|
||||
Reference in New Issue
Block a user