~james/core

core/web/src/lib/auth/orgSessions.test.ts -rw-r--r-- 2.9 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 15 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import type { Did } from "@atcute/lexicons/syntax";
import { TokenRefreshError, type Session } from "@atcute/oauth-browser-client";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { orgDidsControlledBy } from "#lib/auth/accounts.ts";
import {
	revokeAllOrgSessions,
	revokeOrgSessionsFor,
	type OrgSessionDependencies
} from "#lib/auth/orgSessions.ts";

const alice = "did:plc:alice" as Did;
const bob = "did:plc:bob" as Did;
const acme = "did:plc:acme" as Did;
const globex = "did:plc:globex" as Did;
const initech = "did:plc:initech" as Did;

const controllers: Record<string, Did> = { [acme]: alice, [globex]: alice, [initech]: bob };

const signOut = vi.fn(async () => {});
const deps: OrgSessionDependencies = {
	loadOrgDids: vi.fn(() => Object.keys(controllers) as Did[]),
	loadOrgControllers: vi.fn(() => controllers),
	getSession: vi.fn(async (sub: Did) => ({ info: { sub } }) as unknown as Session),
	createAgent: vi.fn(() => ({ signOut })),
	deleteStoredSession: vi.fn(),
	forgetOrgDid: vi.fn()
};

beforeEach(() => {
	vi.clearAllMocks();
});

describe("orgDidsControlledBy", () => {
	it("lists only the orgs minted from the given controller", () => {
		expect(orgDidsControlledBy(controllers, alice).sort()).toEqual([acme, globex].sort());
		expect(orgDidsControlledBy(controllers, "did:plc:nobody" as Did)).toEqual([]);
	});
});

describe("revokeOrgSessionsFor", () => {
	it("revokes the controller's org sessions without a refresh and forgets them", async () => {
		await revokeOrgSessionsFor(alice, deps);

		expect(deps.getSession).toHaveBeenCalledWith(acme, { allowStale: true });
		expect(deps.getSession).toHaveBeenCalledWith(globex, { allowStale: true });
		expect(deps.getSession).not.toHaveBeenCalledWith(initech, expect.anything());
		expect(signOut).toHaveBeenCalledTimes(2);
		expect(deps.forgetOrgDid).toHaveBeenCalledWith(acme);
		expect(deps.forgetOrgDid).toHaveBeenCalledWith(globex);
		expect(deps.forgetOrgDid).not.toHaveBeenCalledWith(initech);
	});

	it("drops the local copy when there is nothing left to revoke", async () => {
		vi.mocked(deps.getSession).mockRejectedValueOnce(
			new TokenRefreshError(acme, "session deleted by another tab")
		);

		await revokeOrgSessionsFor(alice, deps);

		expect(deps.deleteStoredSession).toHaveBeenCalledWith(acme);
		expect(deps.deleteStoredSession).not.toHaveBeenCalledWith(globex);
		expect(deps.forgetOrgDid).toHaveBeenCalledWith(acme);
	});

	it("still forgets an org whose revocation request failed", async () => {
		signOut.mockRejectedValueOnce(new TypeError("fetch failed"));

		await revokeOrgSessionsFor(alice, deps);

		expect(deps.deleteStoredSession).toHaveBeenCalledTimes(1);
		expect(deps.forgetOrgDid).toHaveBeenCalledTimes(2);
	});
});

describe("revokeAllOrgSessions", () => {
	it("revokes every recorded org session and forgets each one", async () => {
		await revokeAllOrgSessions(deps);

		expect(signOut).toHaveBeenCalledTimes(3);
		expect(deps.forgetOrgDid).toHaveBeenCalledTimes(3);
	});
});