mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-02 01:49:51 +08:00
feat(server): passkey pre-refactor (#15060)
#### PR Dependency Tree * **PR #15060** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * OpenApp native sign-in and native session exchange (JWT) for mobile & desktop. * Centralized short-lived auth challenge store for one-time tokens. * Encrypted per-endpoint token storage and native token handlers (Android, iOS, Electron). * **Improvements** * Richer auth-method reporting (password, magic link, OAuth, passkey) and improved sign-in flows. * Hardened magic-link, OAuth, and session issuance; JWT-backed sessions and websocket JWT support. * UX tweaks: form-based password submit, OTP autocomplete, adjusted captcha flow. * **Bug Fixes** * Expanded tests and auth-state resets to avoid cross-test leakage. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
+23
-2
@@ -7,6 +7,14 @@ export interface CacheSetOptions {
|
||||
ttl?: number;
|
||||
}
|
||||
|
||||
const GET_AND_DELETE_LUA = `
|
||||
local value = redis.call("GET", KEYS[1])
|
||||
if value then
|
||||
redis.call("DEL", KEYS[1])
|
||||
end
|
||||
return value
|
||||
`;
|
||||
|
||||
export function isValidCacheTtl(ttl: unknown): ttl is number {
|
||||
return typeof ttl === 'number' && Number.isSafeInteger(ttl) && ttl > 0;
|
||||
}
|
||||
@@ -32,12 +40,15 @@ export class CacheProvider {
|
||||
value: T,
|
||||
opts: CacheSetOptions = {}
|
||||
): Promise<boolean> {
|
||||
if (opts.ttl) {
|
||||
if (isValidCacheTtl(opts.ttl)) {
|
||||
return this.redis
|
||||
.set(key, JSON.stringify(value), 'PX', opts.ttl)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
}
|
||||
if (opts.ttl !== undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.redis
|
||||
.set(key, JSON.stringify(value))
|
||||
@@ -58,12 +69,15 @@ export class CacheProvider {
|
||||
value: T,
|
||||
opts: CacheSetOptions = {}
|
||||
): Promise<boolean> {
|
||||
if (opts.ttl) {
|
||||
if (isValidCacheTtl(opts.ttl)) {
|
||||
return this.redis
|
||||
.set(key, JSON.stringify(value), 'PX', opts.ttl, 'NX')
|
||||
.then(v => !!v)
|
||||
.catch(() => false);
|
||||
}
|
||||
if (opts.ttl !== undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.redis
|
||||
.set(key, JSON.stringify(value), 'NX')
|
||||
@@ -78,6 +92,13 @@ export class CacheProvider {
|
||||
.catch(() => false);
|
||||
}
|
||||
|
||||
async getAndDelete<T = unknown>(key: string): Promise<T | undefined> {
|
||||
return this.redis
|
||||
.eval(GET_AND_DELETE_LUA, 1, key)
|
||||
.then(v => (typeof v === 'string' ? JSON.parse(v) : undefined))
|
||||
.catch(() => undefined);
|
||||
}
|
||||
|
||||
async has(key: string): Promise<boolean> {
|
||||
return this.redis
|
||||
.exists(key)
|
||||
|
||||
@@ -7,12 +7,16 @@ import { GqlArgumentsHost } from '@nestjs/graphql';
|
||||
import type { Request, Response } from 'express';
|
||||
import { ClsServiceManager } from 'nestjs-cls';
|
||||
import type { Socket } from 'socket.io';
|
||||
import { z } from 'zod';
|
||||
|
||||
type RequestResponse = {
|
||||
req: Request;
|
||||
res?: Response;
|
||||
};
|
||||
|
||||
const RequestCookieValueSchema = z.string().min(1);
|
||||
const RequestHeaderValueSchema = z.string().min(1);
|
||||
|
||||
export function getRequestResponseFromHost(
|
||||
host: ArgumentsHost
|
||||
): RequestResponse {
|
||||
@@ -68,9 +72,7 @@ export function getRequestResponseFromContext(
|
||||
export function parseCookies(
|
||||
req: IncomingMessage & { cookies?: Record<string, string> }
|
||||
) {
|
||||
if (req.cookies) {
|
||||
return;
|
||||
}
|
||||
if (req.cookies) return;
|
||||
|
||||
const cookieStr = req.headers.cookie ?? '';
|
||||
req.cookies = cookieStr.split(';').reduce(
|
||||
@@ -103,6 +105,25 @@ export function parseCookies(
|
||||
);
|
||||
}
|
||||
|
||||
export function getRequestCookie(
|
||||
req: IncomingMessage & { cookies?: Record<string, unknown> },
|
||||
name: string
|
||||
) {
|
||||
parseCookies(req as IncomingMessage & { cookies?: Record<string, string> });
|
||||
|
||||
const value = req.cookies?.[name];
|
||||
|
||||
const parsed = RequestCookieValueSchema.safeParse(value);
|
||||
return parsed.success ? parsed.data : undefined;
|
||||
}
|
||||
|
||||
export function getRequestHeader(req: IncomingMessage, name: string) {
|
||||
const value = req.headers[name.toLowerCase()];
|
||||
|
||||
const parsed = RequestHeaderValueSchema.safeParse(value);
|
||||
return parsed.success ? parsed.data : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request type
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user