mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 21:00:41 +00:00
feat: add token revocation endpoint and enhance ciphers import request structure
This commit is contained in:
@@ -18,9 +18,7 @@ function twoFactorRequiredResponse(message: string = 'Two factor required.'): Re
|
||||
error_description: message,
|
||||
TwoFactorProviders: [0],
|
||||
TwoFactorProviders2: {
|
||||
'0': {
|
||||
Priority: 1,
|
||||
},
|
||||
'0': null,
|
||||
},
|
||||
ErrorModel: {
|
||||
Message: message,
|
||||
@@ -115,6 +113,10 @@ export async function handleToken(request: Request, env: Env): Promise<Response>
|
||||
// Optional 2FA: enabled only when TOTP_SECRET is configured in Workers env.
|
||||
let trustedTwoFactorTokenToReturn: string | undefined;
|
||||
if (isTotpEnabled(env.TOTP_SECRET)) {
|
||||
if (twoFactorProvider !== undefined && String(twoFactorProvider) !== '0') {
|
||||
return identityErrorResponse('Unsupported two-factor provider', 'invalid_grant', 400);
|
||||
}
|
||||
|
||||
const rememberRequested = ['1', 'true', 'True', 'TRUE', 'on', 'yes', 'Yes', 'YES'].includes(String(twoFactorRemember || '').trim());
|
||||
|
||||
// Bitwarden may reuse twoFactorToken as a remembered-device token on subsequent logins.
|
||||
@@ -142,7 +144,7 @@ export async function handleToken(request: Request, env: Env): Promise<Response>
|
||||
429
|
||||
);
|
||||
}
|
||||
return identityErrorResponse('Invalid two-factor token', 'invalid_grant', 400);
|
||||
return twoFactorRequiredResponse();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,3 +289,30 @@ export async function handlePrelogin(request: Request, env: Env): Promise<Respon
|
||||
kdfParallelism: kdfParallelism,
|
||||
});
|
||||
}
|
||||
|
||||
// POST /identity/connect/revocation
|
||||
// Best-effort OAuth token revocation endpoint.
|
||||
// RFC 7009 allows returning 200 even if token is unknown.
|
||||
export async function handleRevocation(request: Request, env: Env): Promise<Response> {
|
||||
const storage = new StorageService(env.DB);
|
||||
|
||||
let body: Record<string, string>;
|
||||
const contentType = request.headers.get('content-type') || '';
|
||||
try {
|
||||
if (contentType.includes('application/x-www-form-urlencoded')) {
|
||||
const formData = await request.formData();
|
||||
body = Object.fromEntries(formData.entries()) as Record<string, string>;
|
||||
} else {
|
||||
body = await request.json();
|
||||
}
|
||||
} catch {
|
||||
return new Response(null, { status: 200 });
|
||||
}
|
||||
|
||||
const token = String(body.token || '').trim();
|
||||
if (token) {
|
||||
await storage.deleteRefreshToken(token);
|
||||
}
|
||||
|
||||
return new Response(null, { status: 200 });
|
||||
}
|
||||
|
||||
+44
-37
@@ -8,10 +8,12 @@ import { LIMITS } from '../config/limits';
|
||||
interface CiphersImportRequest {
|
||||
ciphers: Array<{
|
||||
type: number;
|
||||
name: string;
|
||||
name?: string | null;
|
||||
notes?: string | null;
|
||||
favorite?: boolean;
|
||||
reprompt?: number;
|
||||
sshKey?: any | null;
|
||||
key?: string | null;
|
||||
login?: {
|
||||
uris?: Array<{ uri: string | null; match?: number | null }> | null;
|
||||
username?: string | null;
|
||||
@@ -62,6 +64,7 @@ interface CiphersImportRequest {
|
||||
password: string;
|
||||
lastUsedDate: string;
|
||||
}> | null;
|
||||
[key: string]: any;
|
||||
}>;
|
||||
folders: Array<{
|
||||
name: string;
|
||||
@@ -153,61 +156,65 @@ export async function handleCiphersImport(request: Request, env: Env, userId: st
|
||||
userId: userId,
|
||||
type: c.type as CipherType,
|
||||
folderId: folderId,
|
||||
name: c.name || 'Untitled',
|
||||
notes: c.notes || null,
|
||||
favorite: c.favorite || false,
|
||||
name: c.name ?? 'Untitled',
|
||||
notes: c.notes ?? null,
|
||||
favorite: c.favorite ?? false,
|
||||
login: c.login ? {
|
||||
...c.login,
|
||||
username: c.login.username || null,
|
||||
password: c.login.password || null,
|
||||
username: c.login.username ?? null,
|
||||
password: c.login.password ?? null,
|
||||
uris: c.login.uris?.map(u => ({
|
||||
uri: u.uri || null,
|
||||
...u,
|
||||
uri: u.uri ?? null,
|
||||
uriChecksum: null,
|
||||
match: u.match ?? null,
|
||||
})) || null,
|
||||
totp: c.login.totp || null,
|
||||
totp: c.login.totp ?? null,
|
||||
autofillOnPageLoad: c.login.autofillOnPageLoad ?? null,
|
||||
fido2Credentials: c.login.fido2Credentials ?? null,
|
||||
uri: c.login.uri ?? null,
|
||||
passwordRevisionDate: c.login.passwordRevisionDate ?? null,
|
||||
} : null,
|
||||
card: c.card ? {
|
||||
cardholderName: c.card.cardholderName || null,
|
||||
brand: c.card.brand || null,
|
||||
number: c.card.number || null,
|
||||
expMonth: c.card.expMonth || null,
|
||||
expYear: c.card.expYear || null,
|
||||
code: c.card.code || null,
|
||||
...c.card,
|
||||
cardholderName: c.card.cardholderName ?? null,
|
||||
brand: c.card.brand ?? null,
|
||||
number: c.card.number ?? null,
|
||||
expMonth: c.card.expMonth ?? null,
|
||||
expYear: c.card.expYear ?? null,
|
||||
code: c.card.code ?? null,
|
||||
} : null,
|
||||
identity: c.identity ? {
|
||||
title: c.identity.title || null,
|
||||
firstName: c.identity.firstName || null,
|
||||
middleName: c.identity.middleName || null,
|
||||
lastName: c.identity.lastName || null,
|
||||
address1: c.identity.address1 || null,
|
||||
address2: c.identity.address2 || null,
|
||||
address3: c.identity.address3 || null,
|
||||
city: c.identity.city || null,
|
||||
state: c.identity.state || null,
|
||||
postalCode: c.identity.postalCode || null,
|
||||
country: c.identity.country || null,
|
||||
company: c.identity.company || null,
|
||||
email: c.identity.email || null,
|
||||
phone: c.identity.phone || null,
|
||||
ssn: c.identity.ssn || null,
|
||||
username: c.identity.username || null,
|
||||
passportNumber: c.identity.passportNumber || null,
|
||||
licenseNumber: c.identity.licenseNumber || null,
|
||||
...c.identity,
|
||||
title: c.identity.title ?? null,
|
||||
firstName: c.identity.firstName ?? null,
|
||||
middleName: c.identity.middleName ?? null,
|
||||
lastName: c.identity.lastName ?? null,
|
||||
address1: c.identity.address1 ?? null,
|
||||
address2: c.identity.address2 ?? null,
|
||||
address3: c.identity.address3 ?? null,
|
||||
city: c.identity.city ?? null,
|
||||
state: c.identity.state ?? null,
|
||||
postalCode: c.identity.postalCode ?? null,
|
||||
country: c.identity.country ?? null,
|
||||
company: c.identity.company ?? null,
|
||||
email: c.identity.email ?? null,
|
||||
phone: c.identity.phone ?? null,
|
||||
ssn: c.identity.ssn ?? null,
|
||||
username: c.identity.username ?? null,
|
||||
passportNumber: c.identity.passportNumber ?? null,
|
||||
licenseNumber: c.identity.licenseNumber ?? null,
|
||||
} : null,
|
||||
secureNote: c.secureNote || null,
|
||||
secureNote: c.secureNote ?? null,
|
||||
fields: c.fields?.map(f => ({
|
||||
name: f.name || null,
|
||||
value: f.value || null,
|
||||
...f,
|
||||
name: f.name ?? null,
|
||||
value: f.value ?? null,
|
||||
type: f.type,
|
||||
linkedId: f.linkedId ?? null,
|
||||
})) || null,
|
||||
passwordHistory: c.passwordHistory || null,
|
||||
reprompt: c.reprompt || 0,
|
||||
passwordHistory: c.passwordHistory ?? null,
|
||||
reprompt: c.reprompt ?? 0,
|
||||
sshKey: (c as any).sshKey ?? null,
|
||||
key: (c as any).key ?? null,
|
||||
createdAt: now,
|
||||
|
||||
Reference in New Issue
Block a user