@oxyhq/core 6.0.0 → 7.0.0
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.
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/HttpService.js +1 -1
- package/dist/cjs/index.js +17 -11
- package/dist/cjs/mixins/OxyServices.accounts.js +7 -13
- package/dist/cjs/mixins/index.js +0 -5
- package/dist/cjs/server/index.js +2 -2
- package/dist/cjs/shared/utils/debugUtils.js +3 -3
- package/dist/cjs/utils/authWebUrl.js +10 -30
- package/dist/cjs/utils/coldBoot.js +10 -8
- package/dist/cjs/utils/{fapiAutoDetect.js → registrableApex.js} +8 -10
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/HttpService.js +1 -1
- package/dist/esm/index.js +15 -8
- package/dist/esm/mixins/OxyServices.accounts.js +7 -13
- package/dist/esm/mixins/index.js +0 -5
- package/dist/esm/server/index.js +1 -1
- package/dist/esm/shared/utils/debugUtils.js +3 -3
- package/dist/esm/utils/authWebUrl.js +9 -29
- package/dist/esm/utils/coldBoot.js +10 -8
- package/dist/esm/utils/{fapiAutoDetect.js → registrableApex.js} +8 -10
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +1 -1
- package/dist/types/index.d.ts +2 -3
- package/dist/types/mixins/OxyServices.accounts.d.ts +24 -27
- package/dist/types/mixins/index.d.ts +1 -2
- package/dist/types/models/session.d.ts +4 -5
- package/dist/types/server/index.d.ts +1 -1
- package/dist/types/session/SessionClient.d.ts +1 -1
- package/dist/types/shared/utils/debugUtils.d.ts +3 -3
- package/dist/types/utils/accountUtils.d.ts +2 -3
- package/dist/types/utils/authWebUrl.d.ts +9 -29
- package/dist/types/utils/coldBoot.d.ts +17 -14
- package/dist/types/utils/{fapiAutoDetect.d.ts → registrableApex.d.ts} +8 -10
- package/package.json +2 -2
- package/src/HttpService.ts +1 -1
- package/src/index.ts +12 -11
- package/src/mixins/OxyServices.accounts.ts +21 -26
- package/src/mixins/index.ts +0 -7
- package/src/models/session.ts +4 -5
- package/src/server/index.ts +1 -1
- package/src/session/SessionClient.ts +1 -1
- package/src/shared/utils/debugUtils.ts +3 -3
- package/src/utils/__tests__/authWebUrl.test.ts +5 -16
- package/src/utils/__tests__/{fapiAutoDetect.test.ts → registrableApex.test.ts} +1 -1
- package/src/utils/accountUtils.ts +2 -3
- package/src/utils/authWebUrl.ts +9 -30
- package/src/utils/coldBoot.ts +17 -14
- package/src/utils/{fapiAutoDetect.ts → registrableApex.ts} +8 -10
- package/dist/cjs/mixins/OxyServices.authorizedApps.js +0 -38
- package/dist/esm/mixins/OxyServices.authorizedApps.js +0 -35
- package/dist/types/mixins/OxyServices.authorizedApps.d.ts +0 -94
- package/src/mixins/OxyServices.authorizedApps.ts +0 -75
- package/src/mixins/__tests__/authorizedApps.test.ts +0 -63
|
@@ -1,26 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Central IdP apex
|
|
2
|
+
* Central IdP apex constant.
|
|
3
3
|
*
|
|
4
|
-
* `resolveCentralAuthUrl`
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* `resolveCentralAuthUrl` + `CENTRAL_AUTH_URL` were removed in the device-first /
|
|
5
|
+
* legacy-final cutovers. `CENTRAL_IDP_APEX` survives because live server-side
|
|
6
|
+
* callers (the `@oxyhq/core/server` CORS helper + the IdP worker) import it.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { CENTRAL_IDP_APEX } from '../authWebUrl';
|
|
10
10
|
|
|
11
11
|
describe('CENTRAL_IDP_APEX', () => {
|
|
12
12
|
it('is the central IdP registrable apex', () => {
|
|
13
13
|
expect(CENTRAL_IDP_APEX).toBe('oxy.so');
|
|
14
14
|
});
|
|
15
15
|
});
|
|
16
|
-
|
|
17
|
-
describe('CENTRAL_AUTH_URL', () => {
|
|
18
|
-
it('is the central IdP origin with no trailing slash', () => {
|
|
19
|
-
expect(CENTRAL_AUTH_URL).toBe('https://auth.oxy.so');
|
|
20
|
-
expect(CENTRAL_AUTH_URL.endsWith('/')).toBe(false);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('is derived from CENTRAL_IDP_APEX (apex and origin never drift)', () => {
|
|
24
|
-
expect(CENTRAL_AUTH_URL).toBe(`https://auth.${CENTRAL_IDP_APEX}`);
|
|
25
|
-
});
|
|
26
|
-
});
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* surface + the IdP). These cases lock its Public-Suffix-List behaviour.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { registrableApex } from '../
|
|
9
|
+
import { registrableApex } from '../registrableApex';
|
|
10
10
|
|
|
11
11
|
describe('registrableApex', () => {
|
|
12
12
|
it('returns the eTLD+1 for a normal two-label host', () => {
|
|
@@ -14,9 +14,8 @@ export interface QuickAccount {
|
|
|
14
14
|
avatarUrl?: string;
|
|
15
15
|
/**
|
|
16
16
|
* Device-local account slot index, 0..N-1 (Google-style multi-account).
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* remain valid; web flows always populate it after `refreshAllSessions`.
|
|
17
|
+
* Optional so that pre-multi-account QuickAccounts (sessionId-only) remain
|
|
18
|
+
* valid; the device session set populates it where available.
|
|
20
19
|
*/
|
|
21
20
|
authuser?: number;
|
|
22
21
|
/**
|
package/src/utils/authWebUrl.ts
CHANGED
|
@@ -1,38 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Central IdP
|
|
2
|
+
* Central IdP apex constant.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* This module is intentionally pure: it performs no DOM access, reads no
|
|
12
|
-
* `window`/`location`, and has no side effects. It is the single source of
|
|
13
|
-
* truth for the central IdP origin so call sites never hardcode the literal.
|
|
14
|
-
*
|
|
15
|
-
* LEGACY(old-sdk): the client resolver (`resolveCentralAuthUrl`) was removed in
|
|
16
|
-
* the device-first cutover; `CENTRAL_IDP_APEX` / `CENTRAL_AUTH_URL` survive ONLY
|
|
17
|
-
* because the lista-B IdP worker imports them to brand assertions. Deletable
|
|
18
|
-
* once Homiio/Allo/Alia/Syra are bumped off the old SDK AND CloudWatch
|
|
19
|
-
* `/oxy/ecs` shows the `/sso*` + `/fedcm/*` routes quiet — F-final sweep.
|
|
4
|
+
* The client SSO/FedCM resolvers (`resolveCentralAuthUrl`, `CENTRAL_AUTH_URL`)
|
|
5
|
+
* were removed in the device-first / legacy-final cutovers. The lone survivor is
|
|
6
|
+
* `CENTRAL_IDP_APEX`, kept because it has a LIVE consumer —
|
|
7
|
+
* `@oxyhq/core/server`'s CORS helper (`server/cors.ts`'s `createOxyCors`)
|
|
8
|
+
* auto-allows `*.oxy.so` from it. That CORS use is permanent, so this stays
|
|
9
|
+
* past the SSO/FedCM teardown.
|
|
20
10
|
*/
|
|
21
11
|
|
|
22
12
|
/**
|
|
23
13
|
* The registrable apex (eTLD+1) of the Oxy ecosystem's central Identity
|
|
24
|
-
* Provider
|
|
25
|
-
*
|
|
26
|
-
* regardless of which per-apex `auth.<rp>` host served a given request.
|
|
27
|
-
*
|
|
28
|
-
* Kept as a standalone constant so the IdP worker and the SDK derive the same
|
|
29
|
-
* literal from one source of truth (the worker imports it to brand assertions).
|
|
14
|
+
* Provider, reachable at `auth.${CENTRAL_IDP_APEX}`. Single source of truth so
|
|
15
|
+
* the CORS helper (and anything else that needs the central apex) never drifts.
|
|
30
16
|
*/
|
|
31
17
|
export const CENTRAL_IDP_APEX = 'oxy.so';
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* The canonical central Identity Provider origin for the Oxy ecosystem.
|
|
35
|
-
* No trailing slash. Derived from {@link CENTRAL_IDP_APEX} so the apex and the
|
|
36
|
-
* full origin never drift apart. The IdP worker imports it to brand assertions.
|
|
37
|
-
*/
|
|
38
|
-
export const CENTRAL_AUTH_URL = `https://auth.${CENTRAL_IDP_APEX}`;
|
package/src/utils/coldBoot.ts
CHANGED
|
@@ -3,19 +3,21 @@
|
|
|
3
3
|
* authentication resolution.
|
|
4
4
|
*
|
|
5
5
|
* On a fresh page load / app launch the SDK may have several ways to recover an
|
|
6
|
-
* existing session (
|
|
7
|
-
*
|
|
8
|
-
* order
|
|
9
|
-
* skipped. This module encodes exactly that contract and
|
|
6
|
+
* existing session (a persisted refresh-token family, a shared-keychain
|
|
7
|
+
* identity, a cross-domain boot-fragment return, ...). They must be attempted
|
|
8
|
+
* in a deterministic order, and the FIRST one that yields a session wins —
|
|
9
|
+
* every later step is skipped. This module encodes exactly that contract and
|
|
10
|
+
* nothing else.
|
|
10
11
|
*
|
|
11
12
|
* Design constraints (all enforced):
|
|
12
13
|
* - PURE: no DOM, no `navigator`, no `window`, no React, no platform globals.
|
|
13
14
|
* - NO module-level mutable state. Every call to {@link runColdBoot} is fully
|
|
14
15
|
* self-contained, so it is safe under bundler re-evaluation (e.g. the Metro
|
|
15
|
-
* web bundle
|
|
16
|
-
*
|
|
17
|
-
* - Architecture-agnostic:
|
|
18
|
-
*
|
|
16
|
+
* web bundle — the reason any run-once guard for a step must live in the
|
|
17
|
+
* calling consumer, never in a core module-level singleton).
|
|
18
|
+
* - Architecture-agnostic: it knows nothing about HOW a step resolves a
|
|
19
|
+
* session; `runSessionColdBoot` (`boot/coldBootV2.ts`) is the current
|
|
20
|
+
* device-first consumer.
|
|
19
21
|
*
|
|
20
22
|
* A step is skipped (without running) when its `enabled` predicate returns
|
|
21
23
|
* false. Any thrown error — from either `enabled` or `run` — is reported via
|
|
@@ -104,15 +106,16 @@ export interface RunColdBootOptions<S> {
|
|
|
104
106
|
* fails to settle before the deadline, the runner abandons the await for that
|
|
105
107
|
* step (reporting it via `onStepDeadline`) and CONTINUES to the next step,
|
|
106
108
|
* each now racing against an already-expired deadline. This is deliberate:
|
|
107
|
-
* the runner keeps iterating so the TERMINAL step (e.g.
|
|
108
|
-
* whose `run()` performs its side effect
|
|
109
|
-
* `await`) still gets to fire. A step that
|
|
110
|
-
* the deadline simply doesn't settle and is
|
|
109
|
+
* the runner keeps iterating so the TERMINAL step (e.g. `coldBootV2`'s
|
|
110
|
+
* `bootstrap-hop`, whose `run()` performs its navigation side effect
|
|
111
|
+
* synchronously before its first `await`) still gets to fire. A step that
|
|
112
|
+
* has nothing to contribute after the deadline simply doesn't settle and is
|
|
113
|
+
* skipped in turn.
|
|
111
114
|
*
|
|
112
115
|
* Per-step timeouts inside `run()` remain the first line of defense and
|
|
113
116
|
* should keep every step well under this budget on a healthy load; this only
|
|
114
|
-
* trips when one of them regresses (
|
|
115
|
-
* omitted there is no overall deadline.
|
|
117
|
+
* trips when one of them regresses (a step hanging past its own timeout).
|
|
118
|
+
* When omitted there is no overall deadline.
|
|
116
119
|
*/
|
|
117
120
|
readonly overallDeadlineMs?: number;
|
|
118
121
|
/**
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Registrable-apex (eTLD+1) host kernel.
|
|
3
3
|
*
|
|
4
|
-
* The client FAPI auto-detection helper
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* The client FAPI auto-detection helper was removed in the device-first cutover
|
|
5
|
+
* (which is why this file is now named for what it actually is, not the old
|
|
6
|
+
* `fapiAutoDetect`). What survives is the pure registrable-domain kernel, still
|
|
7
|
+
* used server-side by the api's device-first same-apex trust checks
|
|
8
|
+
* (`deviceAuth.ts`'s `POST /auth/device/web-session`, via `sameSite.ts`'s
|
|
9
|
+
* `isSameSiteTrustedRequest`) and the `@oxyhq/core/server` CORS/re-export layer.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* AND CloudWatch `/oxy/ecs` shows the `/sso*` + `/fedcm/*` routes quiet — the
|
|
14
|
-
* F-final sweep should remove this file then.
|
|
11
|
+
* `registrableApex` is NOT legacy — the device-first same-apex check is a live
|
|
12
|
+
* consumer, so this kernel stays regardless of the SSO/FedCM removal.
|
|
15
13
|
*/
|
|
16
14
|
|
|
17
15
|
import { getDomain } from 'tldts';
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.OxyServicesAuthorizedAppsMixin = OxyServicesAuthorizedAppsMixin;
|
|
4
|
-
function OxyServicesAuthorizedAppsMixin(Base) {
|
|
5
|
-
return class extends Base {
|
|
6
|
-
/**
|
|
7
|
-
* List the authenticated user's authorized RP apps — the intersection of the
|
|
8
|
-
* user's FedCM grants and the currently-approved RP catalog. Powers the
|
|
9
|
-
* "Connected apps" management UI. Requires a real user session.
|
|
10
|
-
*/
|
|
11
|
-
async listAuthorizedApps() {
|
|
12
|
-
try {
|
|
13
|
-
const response = await this.makeRequest('GET', '/fedcm/me/authorized-apps', undefined, {
|
|
14
|
-
cache: true,
|
|
15
|
-
cacheTTL: 30 * 1000, // 30 second cache — short, this drives a manageable UI
|
|
16
|
-
});
|
|
17
|
-
return response?.apps ?? [];
|
|
18
|
-
}
|
|
19
|
-
catch (error) {
|
|
20
|
-
throw this.handleError(error);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Revoke the authenticated user's authorization for a specific RP origin.
|
|
25
|
-
* The corresponding cache entry is invalidated so a subsequent
|
|
26
|
-
* `listAuthorizedApps()` sees fresh data.
|
|
27
|
-
*/
|
|
28
|
-
async revokeAuthorizedApp(origin) {
|
|
29
|
-
try {
|
|
30
|
-
await this.makeRequest('DELETE', `/fedcm/me/authorized-apps/${encodeURIComponent(origin)}`, undefined, { cache: false });
|
|
31
|
-
this.clearCacheEntry('GET:/fedcm/me/authorized-apps');
|
|
32
|
-
}
|
|
33
|
-
catch (error) {
|
|
34
|
-
throw this.handleError(error);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
export function OxyServicesAuthorizedAppsMixin(Base) {
|
|
2
|
-
return class extends Base {
|
|
3
|
-
/**
|
|
4
|
-
* List the authenticated user's authorized RP apps — the intersection of the
|
|
5
|
-
* user's FedCM grants and the currently-approved RP catalog. Powers the
|
|
6
|
-
* "Connected apps" management UI. Requires a real user session.
|
|
7
|
-
*/
|
|
8
|
-
async listAuthorizedApps() {
|
|
9
|
-
try {
|
|
10
|
-
const response = await this.makeRequest('GET', '/fedcm/me/authorized-apps', undefined, {
|
|
11
|
-
cache: true,
|
|
12
|
-
cacheTTL: 30 * 1000, // 30 second cache — short, this drives a manageable UI
|
|
13
|
-
});
|
|
14
|
-
return response?.apps ?? [];
|
|
15
|
-
}
|
|
16
|
-
catch (error) {
|
|
17
|
-
throw this.handleError(error);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Revoke the authenticated user's authorization for a specific RP origin.
|
|
22
|
-
* The corresponding cache entry is invalidated so a subsequent
|
|
23
|
-
* `listAuthorizedApps()` sees fresh data.
|
|
24
|
-
*/
|
|
25
|
-
async revokeAuthorizedApp(origin) {
|
|
26
|
-
try {
|
|
27
|
-
await this.makeRequest('DELETE', `/fedcm/me/authorized-apps/${encodeURIComponent(origin)}`, undefined, { cache: false });
|
|
28
|
-
this.clearCacheEntry('GET:/fedcm/me/authorized-apps');
|
|
29
|
-
}
|
|
30
|
-
catch (error) {
|
|
31
|
-
throw this.handleError(error);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
}
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Authorized-apps mixin — the "Connected apps" management surface that survived
|
|
3
|
-
* the FedCM removal.
|
|
4
|
-
*
|
|
5
|
-
* These two methods read/revoke the user's LEGACY FedCM authorization grants
|
|
6
|
-
* (`GET`/`DELETE /fedcm/me/authorized-apps`). They are the last live consumers
|
|
7
|
-
* of that endpoint (services' `ConnectedAppsScreen` via `useAccountQueries` /
|
|
8
|
-
* `useAccountMutations`) and stay until the AppGrant migration retires the
|
|
9
|
-
* FedCM grant store. All FedCM SIGN-IN machinery was deleted with
|
|
10
|
-
* `OxyServices.fedcm.ts`; this is management-only, so it has no dependency on
|
|
11
|
-
* the credential/nonce pipeline.
|
|
12
|
-
*/
|
|
13
|
-
import type { OxyServicesBase } from '../OxyServices.base';
|
|
14
|
-
/**
|
|
15
|
-
* Public summary of an RP application the user has authorized — mirrors the
|
|
16
|
-
* `AuthorizedAppSummary` shape returned by `GET /fedcm/me/authorized-apps`.
|
|
17
|
-
*/
|
|
18
|
-
export interface AuthorizedApp {
|
|
19
|
-
/** Normalised RP origin. */
|
|
20
|
-
origin: string;
|
|
21
|
-
/** Friendly display name. */
|
|
22
|
-
name: string;
|
|
23
|
-
/** Optional human-readable description. */
|
|
24
|
-
description?: string;
|
|
25
|
-
/** ISO-8601 timestamp of when the user first authorized this RP. */
|
|
26
|
-
firstGrantedAt: string;
|
|
27
|
-
/** ISO-8601 timestamp of the most recent FedCM exchange for this user+RP. */
|
|
28
|
-
lastUsedAt: string;
|
|
29
|
-
}
|
|
30
|
-
export declare function OxyServicesAuthorizedAppsMixin<T extends typeof OxyServicesBase>(Base: T): {
|
|
31
|
-
new (...args: any[]): {
|
|
32
|
-
/**
|
|
33
|
-
* List the authenticated user's authorized RP apps — the intersection of the
|
|
34
|
-
* user's FedCM grants and the currently-approved RP catalog. Powers the
|
|
35
|
-
* "Connected apps" management UI. Requires a real user session.
|
|
36
|
-
*/
|
|
37
|
-
listAuthorizedApps(): Promise<AuthorizedApp[]>;
|
|
38
|
-
/**
|
|
39
|
-
* Revoke the authenticated user's authorization for a specific RP origin.
|
|
40
|
-
* The corresponding cache entry is invalidated so a subsequent
|
|
41
|
-
* `listAuthorizedApps()` sees fresh data.
|
|
42
|
-
*/
|
|
43
|
-
revokeAuthorizedApp(origin: string): Promise<void>;
|
|
44
|
-
httpService: import("../HttpService").HttpService;
|
|
45
|
-
cloudURL: string;
|
|
46
|
-
config: import("../OxyServices.base").OxyConfig;
|
|
47
|
-
__resetTokensForTests(): void;
|
|
48
|
-
makeRequest<T_1>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", url: string, data?: any, options?: import("../HttpService").RequestOptions): Promise<T_1>;
|
|
49
|
-
getBaseURL(): string;
|
|
50
|
-
getClient(): import("../HttpService").HttpService;
|
|
51
|
-
createLinkedClient(config: import("../OxyServices.base").OxyConfig): import("..").LinkedHttpClient;
|
|
52
|
-
getMetrics(): {
|
|
53
|
-
totalRequests: number;
|
|
54
|
-
successfulRequests: number;
|
|
55
|
-
failedRequests: number;
|
|
56
|
-
cacheHits: number;
|
|
57
|
-
cacheMisses: number;
|
|
58
|
-
averageResponseTime: number;
|
|
59
|
-
};
|
|
60
|
-
clearCache(): void;
|
|
61
|
-
clearCacheEntry(key: string): void;
|
|
62
|
-
clearCacheByPrefix(prefix: string): number;
|
|
63
|
-
getCacheStats(): {
|
|
64
|
-
size: number;
|
|
65
|
-
hits: number;
|
|
66
|
-
misses: number;
|
|
67
|
-
hitRate: number;
|
|
68
|
-
};
|
|
69
|
-
getCloudURL(): string;
|
|
70
|
-
setTokens(accessToken: string): void;
|
|
71
|
-
clearTokens(): void;
|
|
72
|
-
onTokensChanged(listener: (accessToken: string | null) => void): () => void;
|
|
73
|
-
_cachedUserId: string | null | undefined;
|
|
74
|
-
_cachedAccessToken: string | null;
|
|
75
|
-
getCurrentUserId(): string | null;
|
|
76
|
-
hasValidToken(): boolean;
|
|
77
|
-
getAccessToken(): string | null;
|
|
78
|
-
getAccessTokenExpiry(): number | null;
|
|
79
|
-
waitForAuth(timeoutMs?: number): Promise<boolean>;
|
|
80
|
-
withAuthRetry<T_1>(operation: () => Promise<T_1>, operationName: string, options?: {
|
|
81
|
-
maxRetries?: number;
|
|
82
|
-
retryDelay?: number;
|
|
83
|
-
authTimeoutMs?: number;
|
|
84
|
-
}): Promise<T_1>;
|
|
85
|
-
validate(): Promise<boolean>;
|
|
86
|
-
handleError(error: unknown): Error;
|
|
87
|
-
healthCheck(): Promise<{
|
|
88
|
-
status: string;
|
|
89
|
-
users?: number;
|
|
90
|
-
timestamp?: string;
|
|
91
|
-
[key: string]: any;
|
|
92
|
-
}>;
|
|
93
|
-
};
|
|
94
|
-
} & T;
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Authorized-apps mixin — the "Connected apps" management surface that survived
|
|
3
|
-
* the FedCM removal.
|
|
4
|
-
*
|
|
5
|
-
* These two methods read/revoke the user's LEGACY FedCM authorization grants
|
|
6
|
-
* (`GET`/`DELETE /fedcm/me/authorized-apps`). They are the last live consumers
|
|
7
|
-
* of that endpoint (services' `ConnectedAppsScreen` via `useAccountQueries` /
|
|
8
|
-
* `useAccountMutations`) and stay until the AppGrant migration retires the
|
|
9
|
-
* FedCM grant store. All FedCM SIGN-IN machinery was deleted with
|
|
10
|
-
* `OxyServices.fedcm.ts`; this is management-only, so it has no dependency on
|
|
11
|
-
* the credential/nonce pipeline.
|
|
12
|
-
*/
|
|
13
|
-
import type { OxyServicesBase } from '../OxyServices.base';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Public summary of an RP application the user has authorized — mirrors the
|
|
17
|
-
* `AuthorizedAppSummary` shape returned by `GET /fedcm/me/authorized-apps`.
|
|
18
|
-
*/
|
|
19
|
-
export interface AuthorizedApp {
|
|
20
|
-
/** Normalised RP origin. */
|
|
21
|
-
origin: string;
|
|
22
|
-
/** Friendly display name. */
|
|
23
|
-
name: string;
|
|
24
|
-
/** Optional human-readable description. */
|
|
25
|
-
description?: string;
|
|
26
|
-
/** ISO-8601 timestamp of when the user first authorized this RP. */
|
|
27
|
-
firstGrantedAt: string;
|
|
28
|
-
/** ISO-8601 timestamp of the most recent FedCM exchange for this user+RP. */
|
|
29
|
-
lastUsedAt: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function OxyServicesAuthorizedAppsMixin<T extends typeof OxyServicesBase>(Base: T) {
|
|
33
|
-
return class extends Base {
|
|
34
|
-
/**
|
|
35
|
-
* List the authenticated user's authorized RP apps — the intersection of the
|
|
36
|
-
* user's FedCM grants and the currently-approved RP catalog. Powers the
|
|
37
|
-
* "Connected apps" management UI. Requires a real user session.
|
|
38
|
-
*/
|
|
39
|
-
async listAuthorizedApps(): Promise<AuthorizedApp[]> {
|
|
40
|
-
try {
|
|
41
|
-
const response = await this.makeRequest<{ apps: AuthorizedApp[] }>(
|
|
42
|
-
'GET',
|
|
43
|
-
'/fedcm/me/authorized-apps',
|
|
44
|
-
undefined,
|
|
45
|
-
{
|
|
46
|
-
cache: true,
|
|
47
|
-
cacheTTL: 30 * 1000, // 30 second cache — short, this drives a manageable UI
|
|
48
|
-
},
|
|
49
|
-
);
|
|
50
|
-
return response?.apps ?? [];
|
|
51
|
-
} catch (error) {
|
|
52
|
-
throw this.handleError(error);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Revoke the authenticated user's authorization for a specific RP origin.
|
|
58
|
-
* The corresponding cache entry is invalidated so a subsequent
|
|
59
|
-
* `listAuthorizedApps()` sees fresh data.
|
|
60
|
-
*/
|
|
61
|
-
async revokeAuthorizedApp(origin: string): Promise<void> {
|
|
62
|
-
try {
|
|
63
|
-
await this.makeRequest(
|
|
64
|
-
'DELETE',
|
|
65
|
-
`/fedcm/me/authorized-apps/${encodeURIComponent(origin)}`,
|
|
66
|
-
undefined,
|
|
67
|
-
{ cache: false },
|
|
68
|
-
);
|
|
69
|
-
this.clearCacheEntry('GET:/fedcm/me/authorized-apps');
|
|
70
|
-
} catch (error) {
|
|
71
|
-
throw this.handleError(error);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Authorized-apps mixin tests — the "Connected apps" management pair that
|
|
3
|
-
* survived the FedCM removal. Stubs `makeRequest` (no network).
|
|
4
|
-
*/
|
|
5
|
-
import type { AuthorizedApp } from '../OxyServices.authorizedApps';
|
|
6
|
-
import { OxyServices } from '../../OxyServices';
|
|
7
|
-
|
|
8
|
-
const APP: AuthorizedApp = {
|
|
9
|
-
origin: 'https://mention.earth',
|
|
10
|
-
name: 'Mention',
|
|
11
|
-
firstGrantedAt: '2026-01-01T00:00:00.000Z',
|
|
12
|
-
lastUsedAt: '2026-06-01T00:00:00.000Z',
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
describe('OxyServices.authorizedApps', () => {
|
|
16
|
-
let oxy: OxyServices;
|
|
17
|
-
let makeRequest: jest.SpyInstance;
|
|
18
|
-
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
oxy = new OxyServices({ baseURL: 'http://test.invalid' });
|
|
21
|
-
makeRequest = jest.spyOn(oxy, 'makeRequest');
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
afterEach(() => jest.restoreAllMocks());
|
|
25
|
-
|
|
26
|
-
describe('listAuthorizedApps', () => {
|
|
27
|
-
it('GETs the authorized-apps and returns the array', async () => {
|
|
28
|
-
makeRequest.mockResolvedValueOnce({ apps: [APP] });
|
|
29
|
-
expect(await oxy.listAuthorizedApps()).toEqual([APP]);
|
|
30
|
-
expect(makeRequest).toHaveBeenCalledWith(
|
|
31
|
-
'GET',
|
|
32
|
-
'/fedcm/me/authorized-apps',
|
|
33
|
-
undefined,
|
|
34
|
-
expect.objectContaining({ cache: true }),
|
|
35
|
-
);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('defaults to [] when the response has no apps', async () => {
|
|
39
|
-
makeRequest.mockResolvedValueOnce({});
|
|
40
|
-
expect(await oxy.listAuthorizedApps()).toEqual([]);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('defaults to [] when the response is null (no throw on null access)', async () => {
|
|
44
|
-
makeRequest.mockResolvedValueOnce(null);
|
|
45
|
-
expect(await oxy.listAuthorizedApps()).toEqual([]);
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
describe('revokeAuthorizedApp', () => {
|
|
50
|
-
it('DELETEs the encoded origin and sweeps the list cache', async () => {
|
|
51
|
-
const clearCacheEntry = jest.spyOn(oxy, 'clearCacheEntry');
|
|
52
|
-
makeRequest.mockResolvedValueOnce(undefined);
|
|
53
|
-
await oxy.revokeAuthorizedApp('https://mention.earth');
|
|
54
|
-
expect(makeRequest).toHaveBeenCalledWith(
|
|
55
|
-
'DELETE',
|
|
56
|
-
'/fedcm/me/authorized-apps/https%3A%2F%2Fmention.earth',
|
|
57
|
-
undefined,
|
|
58
|
-
{ cache: false },
|
|
59
|
-
);
|
|
60
|
-
expect(clearCacheEntry).toHaveBeenCalledWith('GET:/fedcm/me/authorized-apps');
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
});
|