@oxyhq/core 12.11.1 → 13.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.
Files changed (41) hide show
  1. package/dist/cjs/.tsbuildinfo +1 -1
  2. package/dist/cjs/index.js +2 -12
  3. package/dist/cjs/mixins/OxyServices.deviceBoot.js +0 -31
  4. package/dist/cjs/server/index.js +1 -6
  5. package/dist/cjs/server/rateLimit.js +3 -0
  6. package/dist/cjs/session/accountDialogController.js +6 -8
  7. package/dist/cjs/utils/oauthPkce.js +1 -5
  8. package/dist/cjs/utils/officialOrigins.js +3 -73
  9. package/dist/esm/.tsbuildinfo +1 -1
  10. package/dist/esm/index.js +2 -3
  11. package/dist/esm/mixins/OxyServices.deviceBoot.js +1 -32
  12. package/dist/esm/server/index.js +1 -1
  13. package/dist/esm/server/rateLimit.js +3 -0
  14. package/dist/esm/session/accountDialogController.js +6 -8
  15. package/dist/esm/utils/oauthPkce.js +0 -4
  16. package/dist/esm/utils/officialOrigins.js +3 -68
  17. package/dist/types/.tsbuildinfo +1 -1
  18. package/dist/types/index.d.ts +3 -5
  19. package/dist/types/mixins/OxyServices.auth.d.ts +1 -12
  20. package/dist/types/mixins/OxyServices.deviceBoot.d.ts +1 -5
  21. package/dist/types/server/index.d.ts +1 -1
  22. package/dist/types/session/accountDialogController.d.ts +9 -15
  23. package/dist/types/utils/oauthPkce.d.ts +11 -7
  24. package/dist/types/utils/officialOrigins.d.ts +3 -13
  25. package/package.json +3 -3
  26. package/src/index.ts +6 -14
  27. package/src/mixins/OxyServices.auth.ts +6 -13
  28. package/src/mixins/OxyServices.deviceBoot.ts +0 -47
  29. package/src/mixins/__tests__/commonsSignIn.test.ts +9 -2
  30. package/src/server/index.ts +1 -8
  31. package/src/server/rateLimit.ts +3 -0
  32. package/src/session/__tests__/accountDialogController.test.ts +3 -5
  33. package/src/session/accountDialogController.ts +12 -18
  34. package/src/utils/__tests__/officialOrigins.test.ts +0 -57
  35. package/src/utils/oauthPkce.ts +11 -9
  36. package/src/utils/officialOrigins.ts +3 -70
  37. package/dist/cjs/session/hubSync.js +0 -55
  38. package/dist/esm/session/hubSync.js +0 -51
  39. package/dist/types/session/hubSync.d.ts +0 -20
  40. package/src/session/__tests__/hubSync.test.ts +0 -51
  41. package/src/session/hubSync.ts +0 -79
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Official first-party web origin allowlist — shared by hub-ticket issuance,
3
- * OAuth redirect validation, and cross-origin session restore.
2
+ * Official first-party web origin allowlist — shared by OAuth redirect
3
+ * validation and the server-side trusted-origin checks.
4
4
  */
5
5
 
6
6
  import { CENTRAL_IDP_APEX } from './authWebUrl';
@@ -20,31 +20,9 @@ const OFFICIAL_APEXES = new Set([
20
20
  'mercaria.co',
21
21
  ]);
22
22
 
23
- export function buildIdpHubOrigin(): string {
24
- return `https://auth.${CENTRAL_IDP_APEX}`;
25
- }
26
-
27
- /** Whether the current web origin is the central IdP hub (`auth.oxy.so`). */
28
- export function isIdpHubOrigin(): boolean {
29
- if (typeof globalThis === 'undefined') {
30
- return false;
31
- }
32
- const location = (globalThis as { location?: Location }).location;
33
- if (!location) {
34
- return false;
35
- }
36
- try {
37
- const { hostname } = new URL(location.href);
38
- return hostname === `auth.${CENTRAL_IDP_APEX}`;
39
- } catch {
40
- return false;
41
- }
42
- }
43
-
44
23
  /**
45
24
  * Whether an origin is a loopback / local-dev origin (`localhost`, `127.0.0.1`,
46
- * or `[::1]` on any port, http or https). Local dev must never be bounced to a
47
- * hosted IdP for cross-origin session restore.
25
+ * or `[::1]` on any port, http or https).
48
26
  */
49
27
  export function isLoopbackOrigin(origin: string): boolean {
50
28
  try {
@@ -80,50 +58,5 @@ export function isOfficialWebOrigin(origin: string): boolean {
80
58
  }
81
59
  }
82
60
 
83
- /** Normalize and validate a return URL against official origins. Returns origin only. */
84
- export function normalizeOfficialReturnOrigin(raw: string): string | null {
85
- try {
86
- const parsed = new URL(raw);
87
- if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
88
- return null;
89
- }
90
- if (!isOfficialWebOrigin(parsed.origin)) {
91
- return null;
92
- }
93
- return parsed.origin;
94
- } catch {
95
- return null;
96
- }
97
- }
98
-
99
- /** Validate a hub-sync return URL; returns the full normalized URL string. */
100
- export function parseHubSyncReturnUrl(raw: string | null): string | null {
101
- if (!raw) {
102
- return null;
103
- }
104
- try {
105
- const parsed = new URL(raw);
106
- if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
107
- return null;
108
- }
109
- if (!isOfficialWebOrigin(parsed.origin)) {
110
- return null;
111
- }
112
- return parsed.toString();
113
- } catch {
114
- return null;
115
- }
116
- }
117
-
118
- /** Build auth.oxy.so/sync redirect URL with a one-time hub ticket. */
119
- export function buildHubSyncUrl(ticket: string, returnUrl?: string): string {
120
- const url = new URL('/sync', buildIdpHubOrigin());
121
- url.searchParams.set('ticket', ticket);
122
- if (returnUrl) {
123
- url.searchParams.set('return', returnUrl);
124
- }
125
- return url.toString();
126
- }
127
-
128
61
  /** @deprecated Use {@link isOfficialWebOrigin}. */
129
62
  export const isAllowedDeviceJoinOrigin = isOfficialWebOrigin;
@@ -1,55 +0,0 @@
1
- "use strict";
2
- /**
3
- * Post-sign-in hub sync — plant device credentials on auth.oxy.so via a
4
- * one-time server ticket (no secrets in URL fragments).
5
- */
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.syncHubAfterSignIn = syncHubAfterSignIn;
8
- exports.redeemHubTicketOnHub = redeemHubTicketOnHub;
9
- const officialOrigins_1 = require("../utils/officialOrigins");
10
- /**
11
- * After a successful sign-in on an official web app, mint a hub ticket and
12
- * redirect to auth.oxy.so/sync so the IdP hub can redeem it and persist the
13
- * shared device credential for silent OAuth restore on other origins.
14
- *
15
- * No-op on native, non-official origins, and when already on the IdP hub.
16
- */
17
- async function syncHubAfterSignIn(oxy, opts) {
18
- if (opts?.enabled === false) {
19
- return false;
20
- }
21
- if (typeof globalThis === 'undefined') {
22
- return false;
23
- }
24
- const location = globalThis.location;
25
- if (!location) {
26
- return false;
27
- }
28
- if ((0, officialOrigins_1.isIdpHubOrigin)()) {
29
- return false;
30
- }
31
- if (!(0, officialOrigins_1.isOfficialWebOrigin)(location.origin)) {
32
- return false;
33
- }
34
- const hubOrigin = (0, officialOrigins_1.buildIdpHubOrigin)();
35
- const issued = await oxy.issueHubTicket(hubOrigin);
36
- const returnUrl = `${location.origin}${location.pathname}${location.search}${location.hash}`;
37
- const syncUrl = (0, officialOrigins_1.buildHubSyncUrl)(issued.ticket, returnUrl);
38
- window.location.assign(syncUrl);
39
- return true;
40
- }
41
- /** Redeem a hub ticket on auth.oxy.so and persist credentials locally. */
42
- async function redeemHubTicketOnHub(oxy, store, ticket) {
43
- const hubOrigin = (0, officialOrigins_1.buildIdpHubOrigin)();
44
- const creds = await oxy.redeemHubTicket(ticket, hubOrigin);
45
- const prior = await store.load();
46
- await store.save({
47
- sessionId: prior?.sessionId ?? '',
48
- userId: prior?.userId ?? '',
49
- deviceId: creds.deviceId,
50
- deviceSecret: creds.deviceSecret,
51
- ...(prior?.accessToken ? { accessToken: prior.accessToken } : {}),
52
- ...(prior?.expiresAt ? { expiresAt: prior.expiresAt } : {}),
53
- });
54
- return true;
55
- }
@@ -1,51 +0,0 @@
1
- /**
2
- * Post-sign-in hub sync — plant device credentials on auth.oxy.so via a
3
- * one-time server ticket (no secrets in URL fragments).
4
- */
5
- import { buildHubSyncUrl, buildIdpHubOrigin, isIdpHubOrigin, isOfficialWebOrigin, } from '../utils/officialOrigins.js';
6
- /**
7
- * After a successful sign-in on an official web app, mint a hub ticket and
8
- * redirect to auth.oxy.so/sync so the IdP hub can redeem it and persist the
9
- * shared device credential for silent OAuth restore on other origins.
10
- *
11
- * No-op on native, non-official origins, and when already on the IdP hub.
12
- */
13
- export async function syncHubAfterSignIn(oxy, opts) {
14
- if (opts?.enabled === false) {
15
- return false;
16
- }
17
- if (typeof globalThis === 'undefined') {
18
- return false;
19
- }
20
- const location = globalThis.location;
21
- if (!location) {
22
- return false;
23
- }
24
- if (isIdpHubOrigin()) {
25
- return false;
26
- }
27
- if (!isOfficialWebOrigin(location.origin)) {
28
- return false;
29
- }
30
- const hubOrigin = buildIdpHubOrigin();
31
- const issued = await oxy.issueHubTicket(hubOrigin);
32
- const returnUrl = `${location.origin}${location.pathname}${location.search}${location.hash}`;
33
- const syncUrl = buildHubSyncUrl(issued.ticket, returnUrl);
34
- window.location.assign(syncUrl);
35
- return true;
36
- }
37
- /** Redeem a hub ticket on auth.oxy.so and persist credentials locally. */
38
- export async function redeemHubTicketOnHub(oxy, store, ticket) {
39
- const hubOrigin = buildIdpHubOrigin();
40
- const creds = await oxy.redeemHubTicket(ticket, hubOrigin);
41
- const prior = await store.load();
42
- await store.save({
43
- sessionId: prior?.sessionId ?? '',
44
- userId: prior?.userId ?? '',
45
- deviceId: creds.deviceId,
46
- deviceSecret: creds.deviceSecret,
47
- ...(prior?.accessToken ? { accessToken: prior.accessToken } : {}),
48
- ...(prior?.expiresAt ? { expiresAt: prior.expiresAt } : {}),
49
- });
50
- return true;
51
- }
@@ -1,20 +0,0 @@
1
- /**
2
- * Post-sign-in hub sync — plant device credentials on auth.oxy.so via a
3
- * one-time server ticket (no secrets in URL fragments).
4
- */
5
- import type { OxyServices } from '../OxyServices';
6
- import type { AuthStateStore } from './authStateStore';
7
- export interface SyncHubAfterSignInOptions {
8
- /** Skip sync when false (OxyProvider hubSync prop). @default true */
9
- enabled?: boolean;
10
- }
11
- /**
12
- * After a successful sign-in on an official web app, mint a hub ticket and
13
- * redirect to auth.oxy.so/sync so the IdP hub can redeem it and persist the
14
- * shared device credential for silent OAuth restore on other origins.
15
- *
16
- * No-op on native, non-official origins, and when already on the IdP hub.
17
- */
18
- export declare function syncHubAfterSignIn(oxy: Pick<OxyServices, 'issueHubTicket'>, opts?: SyncHubAfterSignInOptions): Promise<boolean>;
19
- /** Redeem a hub ticket on auth.oxy.so and persist credentials locally. */
20
- export declare function redeemHubTicketOnHub(oxy: Pick<OxyServices, 'redeemHubTicket'>, store: AuthStateStore, ticket: string): Promise<boolean>;
@@ -1,51 +0,0 @@
1
- import { syncHubAfterSignIn } from '../hubSync';
2
-
3
- jest.mock('../../utils/officialOrigins', () => {
4
- const actual = jest.requireActual('../../utils/officialOrigins');
5
- return {
6
- ...actual,
7
- isIdpHubOrigin: jest.fn(() => false),
8
- isOfficialWebOrigin: jest.fn(() => true),
9
- buildIdpHubOrigin: jest.fn(() => 'https://auth.oxy.so'),
10
- };
11
- });
12
-
13
- describe('syncHubAfterSignIn', () => {
14
- const originalLocation = globalThis.location;
15
- const originalAssign = (globalThis as { location?: Location }).location?.assign;
16
-
17
- afterEach(() => {
18
- Object.defineProperty(globalThis, 'location', {
19
- configurable: true,
20
- value: originalLocation,
21
- });
22
- jest.restoreAllMocks();
23
- });
24
-
25
- it('includes the hash fragment in the hub-sync return URL', async () => {
26
- const assign = jest.fn();
27
- Object.defineProperty(globalThis, 'location', {
28
- configurable: true,
29
- value: {
30
- origin: 'https://oxy.so',
31
- pathname: '/pricing',
32
- search: '?plan=pro',
33
- hash: '#compare',
34
- assign,
35
- },
36
- });
37
- Object.defineProperty(globalThis, 'window', {
38
- configurable: true,
39
- value: { location: globalThis.location },
40
- });
41
-
42
- const issueHubTicket = jest.fn().mockResolvedValue({ ticket: 'tk-1' });
43
-
44
- const redirected = await syncHubAfterSignIn({ issueHubTicket });
45
-
46
- expect(redirected).toBe(true);
47
- expect(issueHubTicket).toHaveBeenCalledWith('https://auth.oxy.so');
48
- const syncUrl = new URL(String(assign.mock.calls[0]?.[0] ?? ''));
49
- expect(syncUrl.searchParams.get('return')).toBe('https://oxy.so/pricing?plan=pro#compare');
50
- });
51
- });
@@ -1,79 +0,0 @@
1
- /**
2
- * Post-sign-in hub sync — plant device credentials on auth.oxy.so via a
3
- * one-time server ticket (no secrets in URL fragments).
4
- */
5
-
6
- import type { OxyServices } from '../OxyServices';
7
- import type { AuthStateStore } from './authStateStore';
8
- import {
9
- buildHubSyncUrl,
10
- buildIdpHubOrigin,
11
- isIdpHubOrigin,
12
- isOfficialWebOrigin,
13
- } from '../utils/officialOrigins';
14
-
15
- export interface SyncHubAfterSignInOptions {
16
- /** Skip sync when false (OxyProvider hubSync prop). @default true */
17
- enabled?: boolean;
18
- }
19
-
20
- /**
21
- * After a successful sign-in on an official web app, mint a hub ticket and
22
- * redirect to auth.oxy.so/sync so the IdP hub can redeem it and persist the
23
- * shared device credential for silent OAuth restore on other origins.
24
- *
25
- * No-op on native, non-official origins, and when already on the IdP hub.
26
- */
27
- export async function syncHubAfterSignIn(
28
- oxy: Pick<OxyServices, 'issueHubTicket'>,
29
- opts?: SyncHubAfterSignInOptions,
30
- ): Promise<boolean> {
31
- if (opts?.enabled === false) {
32
- return false;
33
- }
34
-
35
- if (typeof globalThis === 'undefined') {
36
- return false;
37
- }
38
-
39
- const location = (globalThis as { location?: Location }).location;
40
- if (!location) {
41
- return false;
42
- }
43
-
44
- if (isIdpHubOrigin()) {
45
- return false;
46
- }
47
-
48
- if (!isOfficialWebOrigin(location.origin)) {
49
- return false;
50
- }
51
-
52
- const hubOrigin = buildIdpHubOrigin();
53
- const issued = await oxy.issueHubTicket(hubOrigin);
54
- const returnUrl = `${location.origin}${location.pathname}${location.search}${location.hash}`;
55
- const syncUrl = buildHubSyncUrl(issued.ticket, returnUrl);
56
-
57
- window.location.assign(syncUrl);
58
- return true;
59
- }
60
-
61
- /** Redeem a hub ticket on auth.oxy.so and persist credentials locally. */
62
- export async function redeemHubTicketOnHub(
63
- oxy: Pick<OxyServices, 'redeemHubTicket'>,
64
- store: AuthStateStore,
65
- ticket: string,
66
- ): Promise<boolean> {
67
- const hubOrigin = buildIdpHubOrigin();
68
- const creds = await oxy.redeemHubTicket(ticket, hubOrigin);
69
- const prior = await store.load();
70
- await store.save({
71
- sessionId: prior?.sessionId ?? '',
72
- userId: prior?.userId ?? '',
73
- deviceId: creds.deviceId,
74
- deviceSecret: creds.deviceSecret,
75
- ...(prior?.accessToken ? { accessToken: prior.accessToken } : {}),
76
- ...(prior?.expiresAt ? { expiresAt: prior.expiresAt } : {}),
77
- });
78
- return true;
79
- }