mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-08-05 06:50:10 +00:00
fix: add Bitwarden device registration endpoints
This commit is contained in:
+80
-1
@@ -6,7 +6,7 @@ import { auditRequestMetadata, writeAuditEvent } from '../services/audit-events'
|
|||||||
import { registerMobilePushDevice, unregisterMobilePushDevice } from '../services/push-relay';
|
import { registerMobilePushDevice, unregisterMobilePushDevice } from '../services/push-relay';
|
||||||
import { StorageService } from '../services/storage';
|
import { StorageService } from '../services/storage';
|
||||||
import { errorResponse, jsonResponse } from '../utils/response';
|
import { errorResponse, jsonResponse } from '../utils/response';
|
||||||
import { readKnownDeviceProbe } from '../utils/device';
|
import { readAuthRequestDeviceInfo, readKnownDeviceProbe } from '../utils/device';
|
||||||
import { generateUUID } from '../utils/uuid';
|
import { generateUUID } from '../utils/uuid';
|
||||||
|
|
||||||
const PERMANENT_TRUST_EXPIRES_AT_MS = Date.UTC(2099, 11, 31, 23, 59, 59);
|
const PERMANENT_TRUST_EXPIRES_AT_MS = Date.UTC(2099, 11, 31, 23, 59, 59);
|
||||||
@@ -125,6 +125,85 @@ function parseDeviceName(value: unknown): string {
|
|||||||
return String(value || '').trim().slice(0, 128);
|
return String(value || '').trim().slice(0, 128);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseDeviceType(value: unknown): number | null {
|
||||||
|
if (typeof value === 'number' && Number.isFinite(value)) return Math.max(0, Math.floor(value));
|
||||||
|
const parsed = Number.parseInt(String(value ?? ''), 10);
|
||||||
|
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /api/devices
|
||||||
|
export async function handleRegisterDevice(request: Request, env: Env, userId: string): Promise<Response> {
|
||||||
|
const body = await readJsonBody(request);
|
||||||
|
if (!body) return errorResponse('Invalid request payload', 400);
|
||||||
|
|
||||||
|
const identifier = normalizeIdentifier(body.identifier ?? body.Identifier ?? body.deviceIdentifier ?? body.DeviceIdentifier);
|
||||||
|
const name = parseDeviceName(body.name ?? body.Name ?? body.deviceName ?? body.DeviceName) || 'Unknown device';
|
||||||
|
const type = parseDeviceType(body.type ?? body.Type ?? body.deviceType ?? body.DeviceType);
|
||||||
|
if (!identifier || type == null) return errorResponse('Device identifier and type are required', 400);
|
||||||
|
|
||||||
|
const storage = new StorageService(env.DB);
|
||||||
|
await storage.upsertDevice(userId, identifier, name, type, undefined, parseKeysBody(body));
|
||||||
|
|
||||||
|
const pushToken = String(body.pushToken ?? body.PushToken ?? '').trim();
|
||||||
|
if (pushToken) {
|
||||||
|
const device = await storage.getDevice(userId, identifier);
|
||||||
|
const pushUuid = device?.pushUuid || generateUUID();
|
||||||
|
const updated = await storage.updateDevicePushToken(userId, identifier, pushUuid, pushToken);
|
||||||
|
if (updated) {
|
||||||
|
await registerMobilePushDevice(env, {
|
||||||
|
userId,
|
||||||
|
deviceIdentifier: identifier,
|
||||||
|
type,
|
||||||
|
pushUuid,
|
||||||
|
pushToken,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const device = await storage.getDevice(userId, identifier);
|
||||||
|
if (!device) return errorResponse('Device registration failed', 500);
|
||||||
|
await writeAuditEvent(storage, {
|
||||||
|
actorUserId: userId,
|
||||||
|
action: 'device.register',
|
||||||
|
category: 'device',
|
||||||
|
level: 'info',
|
||||||
|
targetType: 'device',
|
||||||
|
targetId: identifier,
|
||||||
|
metadata: auditRequestMetadata(request),
|
||||||
|
});
|
||||||
|
return jsonResponse(buildDeviceResponse(device));
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /api/devices/lost-trust
|
||||||
|
export async function handleReportLostTrust(request: Request, env: Env, userId: string): Promise<Response> {
|
||||||
|
const body = await readJsonBody(request) || {};
|
||||||
|
const deviceInfo = readAuthRequestDeviceInfo(
|
||||||
|
{
|
||||||
|
deviceIdentifier: String(body.identifier ?? body.Identifier ?? body.deviceIdentifier ?? body.DeviceIdentifier ?? ''),
|
||||||
|
deviceName: String(body.name ?? body.Name ?? body.deviceName ?? body.DeviceName ?? ''),
|
||||||
|
deviceType: String(body.type ?? body.Type ?? body.deviceType ?? body.DeviceType ?? ''),
|
||||||
|
},
|
||||||
|
request
|
||||||
|
);
|
||||||
|
if (!deviceInfo.deviceIdentifier) return errorResponse('Please provide a device identifier', 400);
|
||||||
|
|
||||||
|
const storage = new StorageService(env.DB);
|
||||||
|
await writeAuditEvent(storage, {
|
||||||
|
actorUserId: userId,
|
||||||
|
action: 'device.lost_trust',
|
||||||
|
category: 'device',
|
||||||
|
level: 'warn',
|
||||||
|
targetType: 'device',
|
||||||
|
targetId: deviceInfo.deviceIdentifier,
|
||||||
|
metadata: {
|
||||||
|
deviceIdentifier: deviceInfo.deviceIdentifier,
|
||||||
|
deviceType: deviceInfo.deviceType,
|
||||||
|
...auditRequestMetadata(request),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return new Response(null, { status: 200 });
|
||||||
|
}
|
||||||
|
|
||||||
// GET /api/devices/knowndevice
|
// GET /api/devices/knowndevice
|
||||||
// Compatible with Bitwarden/Vaultwarden behavior:
|
// Compatible with Bitwarden/Vaultwarden behavior:
|
||||||
// - X-Request-Email: base64url(email) without padding
|
// - X-Request-Email: base64url(email) without padding
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ import {
|
|||||||
handleUpdateDeviceToken,
|
handleUpdateDeviceToken,
|
||||||
handleUpdateDeviceWebPushAuth,
|
handleUpdateDeviceWebPushAuth,
|
||||||
handleClearDeviceToken,
|
handleClearDeviceToken,
|
||||||
|
handleRegisterDevice,
|
||||||
|
handleReportLostTrust,
|
||||||
} from './handlers/devices';
|
} from './handlers/devices';
|
||||||
|
|
||||||
function devicesPath(pattern: string): RegExp {
|
function devicesPath(pattern: string): RegExp {
|
||||||
@@ -33,10 +35,15 @@ export async function handleAuthenticatedDeviceRoute(
|
|||||||
): Promise<Response | null> {
|
): Promise<Response | null> {
|
||||||
if (path === '/api/devices' || path === '/devices') {
|
if (path === '/api/devices' || path === '/devices') {
|
||||||
if (method === 'GET') return handleGetDevices(request, env, userId);
|
if (method === 'GET') return handleGetDevices(request, env, userId);
|
||||||
|
if (method === 'POST') return handleRegisterDevice(request, env, userId);
|
||||||
if (method === 'DELETE') return handleDeleteAllDevices(request, env, userId);
|
if (method === 'DELETE') return handleDeleteAllDevices(request, env, userId);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((path === '/api/devices/lost-trust' || path === '/devices/lost-trust') && method === 'POST') {
|
||||||
|
return handleReportLostTrust(request, env, userId);
|
||||||
|
}
|
||||||
|
|
||||||
if (path === '/api/devices/authorized' || path === '/devices/authorized') {
|
if (path === '/api/devices/authorized' || path === '/devices/authorized') {
|
||||||
if (method === 'GET') return handleGetAuthorizedDevices(request, env, userId);
|
if (method === 'GET') return handleGetAuthorizedDevices(request, env, userId);
|
||||||
if (method === 'DELETE') return handleRevokeAllTrustedDevices(request, env, userId);
|
if (method === 'DELETE') return handleRevokeAllTrustedDevices(request, env, userId);
|
||||||
|
|||||||
Reference in New Issue
Block a user