SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
2.0 KB · 41 lines typescript
Raw Blame History
1import 'server-only';2import { cookies } from 'next/headers';3import { and, eq, gt, isNull } from '@/lib/db';4import { newId } from '@rareindex/shared';5import { db, trustedDevices } from '@/lib/db';6import { hmacToken, randomToken } from './crypto';7import { clientInfo, deviceLabel } from './request';8import { DEVICE_COOKIE } from './session';910const TRUST_DAYS = 30;1112/** Is the current browser a trusted device for this user? */13export async function isTrustedDevice(userId: string): Promise<boolean> {14  const raw = (await cookies()).get(DEVICE_COOKIE)?.value;15  if (!raw) return false;16  const rows = await db()17    .select({ id: trustedDevices.id })18    .from(trustedDevices)19    .where(and(eq(trustedDevices.userId, userId), eq(trustedDevices.tokenHash, hmacToken(raw, 'device')), isNull(trustedDevices.revokedAt), gt(trustedDevices.expiresAt, new Date())))20    .limit(1);21  if (!rows[0]) return false;22  void db().update(trustedDevices).set({ lastUsedAt: new Date() }).where(eq(trustedDevices.id, rows[0].id)).catch(() => {});23  return true;24}2526export async function trustThisDevice(userId: string): Promise<void> {27  const raw = randomToken(32);28  const { ip, userAgent } = await clientInfo();29  const expiresAt = new Date(Date.now() + TRUST_DAYS * 86_400_000);30  await db().insert(trustedDevices).values({ id: newId('event'), userId, tokenHash: hmacToken(raw, 'device'), label: deviceLabel(userAgent), userAgent, ip, expiresAt, lastUsedAt: new Date() });31  (await cookies()).set(DEVICE_COOKIE, raw, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', path: '/', expires: expiresAt });32}3334export async function revokeDevice(userId: string, deviceId: string): Promise<void> {35  await db().update(trustedDevices).set({ revokedAt: new Date() }).where(and(eq(trustedDevices.id, deviceId), eq(trustedDevices.userId, userId)));36}3738export async function revokeAllDevices(userId: string): Promise<void> {39  await db().update(trustedDevices).set({ revokedAt: new Date() }).where(and(eq(trustedDevices.userId, userId), isNull(trustedDevices.revokedAt)));40}41