mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-08-05 06:50:10 +00:00
Add Bitwarden push relay support
This commit is contained in:
+40
-9
@@ -3,6 +3,7 @@ import { Env } from '../types';
|
||||
import { getOnlineUserDevices, notifyUserLogout } from '../durable/notifications-hub';
|
||||
import { AuthService } from '../services/auth';
|
||||
import { auditRequestMetadata, writeAuditEvent } from '../services/audit-events';
|
||||
import { registerMobilePushDevice, unregisterMobilePushDevice } from '../services/push-relay';
|
||||
import { StorageService } from '../services/storage';
|
||||
import { errorResponse, jsonResponse } from '../utils/response';
|
||||
import { readKnownDeviceProbe } from '../utils/device';
|
||||
@@ -223,6 +224,8 @@ export async function handleGetAuthorizedDevices(request: Request, env: Env, use
|
||||
encryptedUserKey: null,
|
||||
encryptedPublicKey: null,
|
||||
encryptedPrivateKey: null,
|
||||
pushUuid: null,
|
||||
pushToken: null,
|
||||
devicePendingAuthRequest: null,
|
||||
deviceNote: null,
|
||||
lastSeenAt: null,
|
||||
@@ -325,10 +328,12 @@ export async function handleDeleteDevice(
|
||||
if (!normalized) return errorResponse('Invalid device identifier', 400);
|
||||
|
||||
const storage = new StorageService(env.DB);
|
||||
const device = await storage.getDevice(userId, normalized);
|
||||
await storage.deleteTrustedTwoFactorTokensByDevice(userId, normalized);
|
||||
await storage.deleteRefreshTokensByDevice(userId, normalized);
|
||||
const deleted = await storage.deleteDevice(userId, normalized);
|
||||
if (deleted) {
|
||||
await unregisterMobilePushDevice(env, device?.pushUuid);
|
||||
AuthService.invalidateDeviceCache(userId, normalized);
|
||||
notifyUserLogout(env, userId, normalized);
|
||||
}
|
||||
@@ -537,10 +542,12 @@ export async function handleDeactivateDevice(
|
||||
if (!normalized) return errorResponse('Invalid device identifier', 400);
|
||||
|
||||
const storage = new StorageService(env.DB);
|
||||
const device = await storage.getDevice(userId, normalized);
|
||||
await storage.deleteTrustedTwoFactorTokensByDevice(userId, normalized);
|
||||
await storage.deleteRefreshTokensByDevice(userId, normalized);
|
||||
const deleted = await storage.deleteDevice(userId, normalized);
|
||||
if (deleted) {
|
||||
await unregisterMobilePushDevice(env, device?.pushUuid);
|
||||
AuthService.invalidateDeviceCache(userId, normalized);
|
||||
notifyUserLogout(env, userId, normalized);
|
||||
}
|
||||
@@ -557,18 +564,36 @@ export async function handleDeactivateDevice(
|
||||
}
|
||||
|
||||
// PUT /api/devices/identifier/{deviceIdentifier}/token
|
||||
// Bitwarden mobile reports push token updates to this endpoint.
|
||||
// NodeWarden does not implement push notifications, so accept and no-op.
|
||||
// Bitwarden mobile reports APNs/FCM push token updates to this endpoint.
|
||||
export async function handleUpdateDeviceToken(
|
||||
request: Request,
|
||||
env: Env,
|
||||
userId: string,
|
||||
deviceIdentifier: string
|
||||
): Promise<Response> {
|
||||
void request;
|
||||
void env;
|
||||
void userId;
|
||||
void deviceIdentifier;
|
||||
const normalized = normalizeIdentifier(deviceIdentifier);
|
||||
if (!normalized) return errorResponse('Invalid device identifier', 400);
|
||||
|
||||
const body = await readJsonBody(request);
|
||||
const pushToken = String(body?.pushToken ?? body?.PushToken ?? '').trim();
|
||||
if (!pushToken) return errorResponse('Invalid push token', 400);
|
||||
|
||||
const storage = new StorageService(env.DB);
|
||||
const device = await storage.getDevice(userId, normalized);
|
||||
if (!device) return errorResponse('Device not found', 404);
|
||||
|
||||
const pushUuid = device.pushUuid || generateUUID();
|
||||
const updated = await storage.updateDevicePushToken(userId, normalized, pushUuid, pushToken);
|
||||
if (updated) {
|
||||
await registerMobilePushDevice(env, {
|
||||
userId,
|
||||
deviceIdentifier: normalized,
|
||||
type: device.type,
|
||||
pushUuid,
|
||||
pushToken,
|
||||
});
|
||||
}
|
||||
|
||||
return new Response(null, { status: 200 });
|
||||
}
|
||||
|
||||
@@ -594,9 +619,15 @@ export async function handleClearDeviceToken(
|
||||
deviceIdentifier: string
|
||||
): Promise<Response> {
|
||||
void request;
|
||||
void env;
|
||||
void userId;
|
||||
void deviceIdentifier;
|
||||
const normalized = normalizeIdentifier(deviceIdentifier);
|
||||
if (!normalized) return errorResponse('Invalid device identifier', 400);
|
||||
|
||||
const storage = new StorageService(env.DB);
|
||||
const cleared = await storage.clearDevicePushToken(userId, normalized);
|
||||
if (cleared?.pushUuid) {
|
||||
await unregisterMobilePushDevice(env, cleared.pushUuid);
|
||||
}
|
||||
|
||||
return new Response(null, { status: 200 });
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { readAuthRequestDeviceInfo } from '../utils/device';
|
||||
import { createRecoveryCode, recoveryCodeEquals } from '../utils/recovery-code';
|
||||
import { generateUUID } from '../utils/uuid';
|
||||
import { issueSendAccessToken } from './sends';
|
||||
import { registerMobilePushDevice } from '../services/push-relay';
|
||||
import {
|
||||
buildAccountKeys,
|
||||
buildUserDecryptionOptions,
|
||||
@@ -50,6 +51,44 @@ async function resolveDeviceSession(
|
||||
return { identifier: deviceInfo.deviceIdentifier, sessionStamp };
|
||||
}
|
||||
|
||||
function readDevicePushToken(body: Record<string, string>): string {
|
||||
return String(readBodyValue(body, ['devicePushToken', 'DevicePushToken', 'device_push_token']) || '').trim();
|
||||
}
|
||||
|
||||
async function persistIdentityDevicePushToken(
|
||||
env: Env,
|
||||
storage: StorageService,
|
||||
userId: string,
|
||||
deviceSession: { identifier: string; sessionStamp: string } | null,
|
||||
deviceType: number,
|
||||
body: Record<string, string>
|
||||
): Promise<void> {
|
||||
if (!deviceSession) return;
|
||||
const pushToken = readDevicePushToken(body);
|
||||
if (!pushToken) return;
|
||||
|
||||
const device = await storage.getDevice(userId, deviceSession.identifier);
|
||||
if (!device) return;
|
||||
|
||||
const pushUuid = device.pushUuid || generateUUID();
|
||||
await storage.updateDevicePushToken(userId, deviceSession.identifier, pushUuid, pushToken);
|
||||
const registered = await registerMobilePushDevice(env, {
|
||||
userId,
|
||||
deviceIdentifier: deviceSession.identifier,
|
||||
type: device.type || deviceType,
|
||||
pushUuid,
|
||||
pushToken,
|
||||
});
|
||||
console.info('Mobile push token updated from identity token request', {
|
||||
userId,
|
||||
deviceIdentifier: deviceSession.identifier,
|
||||
deviceType: device.type || deviceType,
|
||||
pushUuid,
|
||||
pushTokenLength: pushToken.length,
|
||||
relayRegistered: registered,
|
||||
});
|
||||
}
|
||||
|
||||
function shouldUseWebSession(request: Request): boolean {
|
||||
return String(request.headers.get('X-NodeWarden-Web-Session') || '').trim() === '1';
|
||||
}
|
||||
@@ -414,6 +453,7 @@ export async function handleToken(request: Request, env: Env): Promise<Response>
|
||||
deviceInfo.deviceType,
|
||||
deviceSession.sessionStamp
|
||||
);
|
||||
await persistIdentityDevicePushToken(env, storage, user.id, deviceSession, deviceInfo.deviceType, body);
|
||||
}
|
||||
|
||||
// Successful login - clear failed attempts
|
||||
@@ -536,6 +576,7 @@ export async function handleToken(request: Request, env: Env): Promise<Response>
|
||||
deviceInfo.deviceType,
|
||||
deviceSession.sessionStamp
|
||||
);
|
||||
await persistIdentityDevicePushToken(env, storage, user.id, deviceSession, deviceInfo.deviceType, body);
|
||||
}
|
||||
|
||||
await rateLimit.clearLoginAttempts(loginIdentifier);
|
||||
@@ -664,6 +705,7 @@ export async function handleToken(request: Request, env: Env): Promise<Response>
|
||||
deviceInfo.deviceType,
|
||||
deviceSession.sessionStamp
|
||||
);
|
||||
await persistIdentityDevicePushToken(env, storage, user.id, deviceSession, deviceInfo.deviceType, body);
|
||||
}
|
||||
|
||||
// Successful login - clear failed attempts
|
||||
|
||||
Reference in New Issue
Block a user