~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
120 lines · 4.6 KB Raw History
// OAuth sessions record the device (User-Agent) that opened them, so two
// sessions of the same app can be told apart in the Apps list. formatUserAgent
// renders a short "Browser · OS" label, and listConnectedApps surfaces the
// stored value.

import { describe, expect, it } from 'vitest';
import { formatUserAgent } from '../packages/account-ui/src/lib/userAgent.js';
import { createAccountHandlers } from '../packages/core/src/handlers/account.js';

describe('formatUserAgent', () => {
  it('parses common browser + OS pairs', () => {
    expect(
      formatUserAgent(
        'Mozilla/5.0 (Linux; Android 15; Pixel 9) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Mobile Safari/537.36',
      ),
    ).toBe('Chrome · Android');
    expect(
      formatUserAgent(
        'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15',
      ),
    ).toBe('Safari · macOS');
    expect(
      formatUserAgent(
        'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0',
      ),
    ).toBe('Firefox · Windows');
    expect(
      formatUserAgent(
        'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1',
      ),
    ).toBe('Safari · iPhone');
  });

  it('names common non-browser HTTP clients', () => {
    expect(formatUserAgent('node')).toBe('Node.js');
    expect(formatUserAgent('curl/8.7.1')).toBe('curl');
    expect(formatUserAgent('okhttp/4.9.0')).toBe('OkHttp');
    expect(formatUserAgent('Go-http-client/1.1')).toBe('Go');
    expect(formatUserAgent('python-requests/2.32.3')).toBe('Python');
  });

  it('falls back and handles missing values', () => {
    expect(formatUserAgent(null)).toBeNull();
    expect(formatUserAgent('')).toBeNull();
    // A genuinely unrecognized agent keeps its trimmed raw value.
    expect(formatUserAgent('SomeUnknownBot/1.0')).toBe('SomeUnknownBot/1.0');
  });
});

describe('listConnectedApps surfaces the device', () => {
  it('returns the stored userAgent for a session', async () => {
    const account = createAccountHandlers(
      /** @type {any} */ ({
        jwtSecret: 'test-secret',
        sharedStorage: {
          listOAuthTokensByDid: async () => [
            {
              did: 'did:plc:x',
              clientId: 'https://client.example/metadata.json',
              scope: 'atproto',
              dpopJkt: 'abcdef123456',
              createdAt: Date.now(),
              userAgent: 'Mozilla/5.0 … Chrome/150 Mobile',
              tokenId: 'ref-1',
            },
          ],
        },
      }),
    );
    const apps = await account.listConnectedApps('did:plc:x');
    expect(apps).toHaveLength(1);
    expect(apps[0].userAgent).toBe('Mozilla/5.0 … Chrome/150 Mobile');
  });

  it('orders sessions by last access and falls back to createdAt', async () => {
    const account = createAccountHandlers(
      /** @type {any} */ ({
        jwtSecret: 'test-secret',
        sharedStorage: {
          listOAuthTokensByDid: async () => [
            // Authorized first, but refreshed most recently -> sorts to the top.
            {
              did: 'did:plc:x',
              clientId: 'https://old.example/metadata.json',
              createdAt: Date.parse('2026-01-01T00:00:00Z'),
              updatedAt: Date.parse('2026-08-01T00:00:00Z'),
              tokenId: 'ref-old',
            },
            // Authorized later but never refreshed since.
            {
              did: 'did:plc:x',
              clientId: 'https://new.example/metadata.json',
              createdAt: Date.parse('2026-07-01T00:00:00Z'),
              updatedAt: Date.parse('2026-07-01T00:00:00Z'),
              tokenId: 'ref-new',
            },
            // Legacy token with no updatedAt: createdAt stands in.
            {
              did: 'did:plc:x',
              clientId: 'https://legacy.example/metadata.json',
              createdAt: Date.parse('2026-06-01T00:00:00Z'),
              tokenId: 'ref-legacy',
            },
          ],
        },
      }),
    );
    const apps = await account.listConnectedApps('did:plc:x');
    expect(apps.map((a) => a.clientId)).toEqual([
      'https://old.example/metadata.json',
      'https://new.example/metadata.json',
      'https://legacy.example/metadata.json',
    ]);
    // authorizedAt tracks first authorization; lastAccessedAt tracks the refresh.
    expect(apps[0].authorizedAt).toBe('2026-01-01');
    expect(apps[0].lastAccessedAt).toBe('2026-08-01T00:00:00.000Z');
    // Legacy token with no updatedAt reports createdAt as its last access.
    expect(apps[2].lastAccessedAt).toBe('2026-06-01T00:00:00.000Z');
  });
});