@topolo/sdk 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,165 +0,0 @@
1
- import { beforeEach, describe, expect, it, vi } from 'vitest';
2
- import { TopoloClient } from '../client.js';
3
- import { IdentityModule } from './identity.js';
4
-
5
- const AGENT = { clientName: 'topolo-sdk-test', clientVersion: '0.0.0' };
6
-
7
- function stubFetch(responses: Array<{ status?: number; body?: unknown }>) {
8
- const calls: Array<{ url: string; init: RequestInit }> = [];
9
- const impl = vi.fn(async (url: string, init: RequestInit) => {
10
- calls.push({ url, init });
11
- if (String(url).includes('/api/services/by-slug/')) {
12
- return new Response(JSON.stringify({ success: true, data: { app_id: 'service_auth' } }), {
13
- status: 200,
14
- headers: { 'Content-Type': 'application/json' },
15
- });
16
- }
17
- if (String(url).includes('/api/services/catalog')) {
18
- return new Response(JSON.stringify({ data: { services: [] } }), {
19
- status: 200,
20
- headers: { 'Content-Type': 'application/json' },
21
- });
22
- }
23
- const next = responses.shift() ?? { status: 200, body: {} };
24
- return new Response(JSON.stringify(next.body ?? {}), {
25
- status: next.status ?? 200,
26
- headers: { 'Content-Type': 'application/json' },
27
- });
28
- });
29
- return { impl: impl as unknown as typeof fetch, calls };
30
- }
31
-
32
- function requestCalls(calls: Array<{ url: string; init: RequestInit }>) {
33
- return calls.filter(
34
- (call) =>
35
- !call.url.includes('/api/services/by-slug/') &&
36
- !call.url.includes('/api/services/catalog'),
37
- );
38
- }
39
-
40
- function clientWith(
41
- impl: typeof fetch,
42
- credential: { kind: 'access_token'; accessToken: string } | { kind: 'api_key'; apiKey: string } = {
43
- kind: 'access_token',
44
- accessToken: 'tok_test',
45
- },
46
- ) {
47
- return new TopoloClient({
48
- credential,
49
- agent: AGENT,
50
- fetch: impl,
51
- appIds: { auth: 'service_auth' },
52
- });
53
- }
54
-
55
- beforeEach(() => {
56
- TopoloClient.clearAppIdCache();
57
- });
58
-
59
- describe('IdentityModule.whoami', () => {
60
- it('resolves a data-enveloped access-token identity with an organization', async () => {
61
- const { impl, calls } = stubFetch([
62
- {
63
- body: {
64
- data: {
65
- credentialType: 'access_token',
66
- user: { id: 'u_1', email: 'ada@example.com', name: 'Ada', role: 'owner' },
67
- organization: { id: 'org_1', slug: 'acme', name: 'Acme Inc' },
68
- permissions: ['crm.contacts:read'],
69
- },
70
- },
71
- },
72
- ]);
73
- const identity = new IdentityModule(clientWith(impl));
74
-
75
- const me = await identity.whoami();
76
-
77
- expect(me).toEqual({
78
- kind: 'access_token',
79
- user: {
80
- id: 'u_1',
81
- email: 'ada@example.com',
82
- name: 'Ada',
83
- role: 'owner',
84
- permissions: ['crm.contacts:read'],
85
- },
86
- organization: { id: 'org_1', slug: 'acme', name: 'Acme Inc' },
87
- });
88
- expect(new URL(requestCalls(calls)[0]!.url).pathname).toBe('/api/auth/me');
89
- });
90
-
91
- it('defaults api_key role to "service" and reads a bare (non-enveloped) payload', async () => {
92
- const { impl } = stubFetch([
93
- {
94
- body: {
95
- credentialType: 'api_key',
96
- user: { id: 'usr_1', email: 'user@example.com' },
97
- organization: { id: 'org_2', slug: 'beta' },
98
- },
99
- },
100
- ]);
101
- const identity = new IdentityModule(
102
- clientWith(impl, { kind: 'api_key', apiKey: 'dak_xyz' }),
103
- );
104
-
105
- const me = await identity.whoami();
106
-
107
- expect(me.kind).toBe('api_key');
108
- expect(me.user.role).toBe('service');
109
- expect(me.user.name).toBeNull();
110
- // organization name falls back to slug when name is absent
111
- expect(me.organization).toEqual({ id: 'org_2', slug: 'beta', name: 'beta' });
112
- });
113
-
114
- it('defaults access_token role to "member" and reads permissions from user when top-level absent', async () => {
115
- const { impl } = stubFetch([
116
- {
117
- body: {
118
- data: {
119
- user: {
120
- id: 'u_3',
121
- email: 'm@example.com',
122
- permissions: ['mail.messages:read'],
123
- },
124
- },
125
- },
126
- },
127
- ]);
128
- const identity = new IdentityModule(clientWith(impl));
129
-
130
- const me = await identity.whoami();
131
-
132
- expect(me.kind).toBe('access_token');
133
- expect(me.user.role).toBe('member');
134
- expect(me.user.permissions).toEqual(['mail.messages:read']);
135
- expect(me.organization).toBeNull();
136
- });
137
-
138
- it('synthesizes an organization from user.orgId/orgSlug when no organization object is present', async () => {
139
- const { impl } = stubFetch([
140
- {
141
- body: {
142
- data: {
143
- user: { id: 'u_4', email: 'o@example.com', orgId: 'org_9', orgSlug: 'gamma' },
144
- },
145
- },
146
- },
147
- ]);
148
- const identity = new IdentityModule(clientWith(impl));
149
-
150
- const me = await identity.whoami();
151
-
152
- expect(me.organization).toEqual({ id: 'org_9', slug: 'gamma', name: 'gamma' });
153
- });
154
-
155
- it('defaults missing permissions to an empty array', async () => {
156
- const { impl } = stubFetch([
157
- { body: { data: { user: { id: 'u_5', email: 'n@example.com' } } } },
158
- ]);
159
- const identity = new IdentityModule(clientWith(impl));
160
-
161
- const me = await identity.whoami();
162
-
163
- expect(me.user.permissions).toEqual([]);
164
- });
165
- });
@@ -1,18 +0,0 @@
1
- import type { CredentialIntrospection, TopoloClient } from '../client.js';
2
-
3
- /**
4
- * Identity module — wraps TopoloAuth's caller-identity endpoints.
5
- *
6
- * All responses are scoped to the organization the credential was issued for;
7
- * there is no way to ask this module about a different org.
8
- */
9
- export class IdentityModule {
10
- constructor(private readonly client: TopoloClient) {}
11
-
12
- /** Returns the resolved user, organization, and permission set for the
13
- * current credential. Equivalent to `TopoloClient.introspect()` — exposed
14
- * on this module for symmetry with the other domain modules. */
15
- whoami(): Promise<CredentialIntrospection> {
16
- return this.client.introspect();
17
- }
18
- }