@oxyhq/core 3.10.0 → 3.11.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/AuthManager.js +9 -2
- package/dist/cjs/HttpService.js +27 -9
- package/dist/cjs/OxyServices.base.js +3 -2
- package/dist/cjs/crypto/canonicalJson.js +107 -0
- package/dist/cjs/crypto/keyManager.js +67 -8
- package/dist/cjs/crypto/signatureService.js +103 -0
- package/dist/cjs/i18n/locales/en-US.json +9 -0
- package/dist/cjs/i18n/locales/es-ES.json +9 -0
- package/dist/cjs/i18n/locales/locales/en-US.json +9 -0
- package/dist/cjs/i18n/locales/locales/es-ES.json +9 -0
- package/dist/cjs/index.js +15 -5
- package/dist/cjs/mixins/OxyServices.assets.js +45 -7
- package/dist/cjs/mixins/OxyServices.auth.js +190 -1
- package/dist/cjs/mixins/OxyServices.identity.js +291 -0
- package/dist/cjs/mixins/OxyServices.sso.js +28 -1
- package/dist/cjs/mixins/OxyServices.user.js +1 -0
- package/dist/cjs/mixins/OxyServices.utility.js +52 -23
- package/dist/cjs/mixins/index.js +3 -0
- package/dist/cjs/server/cors.js +20 -21
- package/dist/cjs/server/rateLimit.js +32 -8
- package/dist/cjs/utils/fapiAutoDetect.js +12 -42
- package/dist/cjs/utils/ssoReturn.js +1 -1
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/AuthManager.js +9 -2
- package/dist/esm/HttpService.js +27 -9
- package/dist/esm/OxyServices.base.js +3 -2
- package/dist/esm/crypto/canonicalJson.js +104 -0
- package/dist/esm/crypto/keyManager.js +67 -8
- package/dist/esm/crypto/signatureService.js +102 -0
- package/dist/esm/i18n/locales/en-US.json +9 -0
- package/dist/esm/i18n/locales/es-ES.json +9 -0
- package/dist/esm/i18n/locales/locales/en-US.json +9 -0
- package/dist/esm/i18n/locales/locales/es-ES.json +9 -0
- package/dist/esm/index.js +10 -2
- package/dist/esm/mixins/OxyServices.assets.js +45 -7
- package/dist/esm/mixins/OxyServices.auth.js +190 -1
- package/dist/esm/mixins/OxyServices.identity.js +287 -0
- package/dist/esm/mixins/OxyServices.sso.js +28 -1
- package/dist/esm/mixins/OxyServices.user.js +1 -0
- package/dist/esm/mixins/OxyServices.utility.js +52 -23
- package/dist/esm/mixins/index.js +3 -0
- package/dist/esm/server/cors.js +20 -21
- package/dist/esm/server/rateLimit.js +32 -8
- package/dist/esm/utils/fapiAutoDetect.js +12 -41
- package/dist/esm/utils/ssoReturn.js +1 -1
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/HttpService.d.ts +3 -0
- package/dist/types/OxyServices.d.ts +2 -2
- package/dist/types/crypto/canonicalJson.d.ts +44 -0
- package/dist/types/crypto/keyManager.d.ts +7 -0
- package/dist/types/crypto/signatureService.d.ts +61 -0
- package/dist/types/index.d.ts +7 -3
- package/dist/types/mixins/OxyServices.assets.d.ts +6 -1
- package/dist/types/mixins/OxyServices.auth.d.ts +136 -0
- package/dist/types/mixins/OxyServices.identity.d.ts +249 -0
- package/dist/types/mixins/OxyServices.sso.d.ts +4 -1
- package/dist/types/mixins/OxyServices.utility.d.ts +3 -3
- package/dist/types/mixins/index.d.ts +2 -1
- package/dist/types/models/interfaces.d.ts +3 -0
- package/dist/types/server/cors.d.ts +5 -5
- package/dist/types/utils/fapiAutoDetect.d.ts +6 -23
- package/dist/types/utils/ssoReturn.d.ts +1 -1
- package/package.json +3 -2
- package/src/AuthManager.ts +8 -2
- package/src/HttpService.ts +36 -8
- package/src/OxyServices.base.ts +3 -2
- package/src/OxyServices.ts +1 -1
- package/src/__tests__/authManager.security.test.ts +31 -0
- package/src/__tests__/authSocket.test.ts +96 -0
- package/src/__tests__/httpServiceCsrf.test.ts +75 -0
- package/src/crypto/__tests__/canonicalJson.test.ts +116 -0
- package/src/crypto/__tests__/keyManager.atomicity.test.ts +41 -2
- package/src/crypto/__tests__/signChallengeShared.test.ts +64 -0
- package/src/crypto/__tests__/signedRecord.test.ts +125 -0
- package/src/crypto/canonicalJson.ts +120 -0
- package/src/crypto/keyManager.ts +62 -12
- package/src/crypto/signatureService.ts +126 -0
- package/src/i18n/locales/en-US.json +9 -0
- package/src/i18n/locales/es-ES.json +9 -0
- package/src/index.ts +28 -3
- package/src/mixins/OxyServices.assets.ts +56 -7
- package/src/mixins/OxyServices.auth.ts +309 -1
- package/src/mixins/OxyServices.identity.ts +445 -0
- package/src/mixins/OxyServices.sso.ts +30 -1
- package/src/mixins/OxyServices.user.ts +1 -0
- package/src/mixins/OxyServices.utility.ts +57 -23
- package/src/mixins/__tests__/OxyServices.identity.test.ts +364 -0
- package/src/mixins/__tests__/assetCredentials.test.ts +47 -0
- package/src/mixins/__tests__/assetUpload.test.ts +191 -0
- package/src/mixins/__tests__/commonsSignIn.test.ts +277 -0
- package/src/mixins/__tests__/getFileDownloadUrl.test.ts +13 -0
- package/src/mixins/__tests__/serviceAuth.test.ts +49 -2
- package/src/mixins/__tests__/sso.test.ts +31 -0
- package/src/mixins/index.ts +4 -0
- package/src/models/interfaces.ts +3 -0
- package/src/server/__tests__/cors.test.ts +5 -1
- package/src/server/__tests__/rateLimit.test.ts +116 -0
- package/src/server/cors.ts +25 -20
- package/src/server/rateLimit.ts +39 -8
- package/src/utils/__tests__/consumeSsoReturn.test.ts +1 -1
- package/src/utils/__tests__/fapiAutoDetect.test.ts +40 -11
- package/src/utils/__tests__/ssoReturn.test.ts +1 -1
- package/src/utils/fapiAutoDetect.ts +12 -39
- package/src/utils/ssoReturn.ts +2 -2
|
@@ -688,33 +688,45 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
688
688
|
}
|
|
689
689
|
return next(new Error('Invalid token'));
|
|
690
690
|
}
|
|
691
|
-
const
|
|
692
|
-
if (!
|
|
691
|
+
const claimedUserId = decoded.userId || decoded.id;
|
|
692
|
+
if (!claimedUserId) {
|
|
693
693
|
return next(new Error('Invalid token payload'));
|
|
694
694
|
}
|
|
695
695
|
// Check expiration — reject tokens at exact expiry second (use <=)
|
|
696
696
|
if (decoded.exp && decoded.exp <= Math.floor(Date.now() / 1000)) {
|
|
697
697
|
return next(new Error('Token expired'));
|
|
698
698
|
}
|
|
699
|
-
//
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
699
|
+
// A server-validated session is mandatory. A bare decoded JWT proves
|
|
700
|
+
// nothing — the signature is not verified here, so without a session
|
|
701
|
+
// round-trip a forged token could claim any user id.
|
|
702
|
+
if (!decoded.sessionId) {
|
|
703
|
+
return next(new Error('Session required'));
|
|
704
|
+
}
|
|
705
|
+
let userId = claimedUserId;
|
|
706
|
+
try {
|
|
707
|
+
const result = await oxyInstance.validateSession(decoded.sessionId, {
|
|
708
|
+
useHeaderValidation: true,
|
|
709
|
+
});
|
|
710
|
+
if (!result || !result.valid || !result.user) {
|
|
711
|
+
return next(new Error('Session invalid'));
|
|
708
712
|
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
}
|
|
716
|
-
return next(new Error('Session validation failed'));
|
|
713
|
+
// The session is the source of truth. The client-claimed user id
|
|
714
|
+
// must match the server-validated identity, otherwise a valid
|
|
715
|
+
// session could be paired with a forged user id.
|
|
716
|
+
const validatedUserId = getUserIdentityId(result.user);
|
|
717
|
+
if (!validatedUserId || validatedUserId !== claimedUserId) {
|
|
718
|
+
return next(new Error('Session user mismatch'));
|
|
717
719
|
}
|
|
720
|
+
userId = validatedUserId;
|
|
721
|
+
}
|
|
722
|
+
catch (validateErr) {
|
|
723
|
+
if (debug) {
|
|
724
|
+
logger.debug('[oxy.authSocket] Session validation failed', {
|
|
725
|
+
component: 'auth',
|
|
726
|
+
method: 'authSocket',
|
|
727
|
+
}, validateErr);
|
|
728
|
+
}
|
|
729
|
+
return next(new Error('Session validation failed'));
|
|
718
730
|
}
|
|
719
731
|
// Attach user data to socket. We expose BOTH `socket.data.userId`
|
|
720
732
|
// (the official Socket.IO data slot) and `socket.user` because
|
|
@@ -779,9 +791,9 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
779
791
|
* Express.js middleware that enforces a specific service-token scope.
|
|
780
792
|
*
|
|
781
793
|
* Mount AFTER `auth()` / `serviceAuth()` — relies on `req.serviceApp` and
|
|
782
|
-
* (when delegation is in effect) `req.serviceActingAs.scopes`.
|
|
783
|
-
*
|
|
784
|
-
* the app
|
|
794
|
+
* (when delegation is in effect) `req.serviceActingAs.scopes`. App-only
|
|
795
|
+
* service requests require the app scope. Delegated user requests require
|
|
796
|
+
* BOTH the app scope and the per-user delegation scope.
|
|
785
797
|
*
|
|
786
798
|
* Requests authenticated as a regular user (no service token) are rejected
|
|
787
799
|
* with 403 — scope-protected endpoints are service-to-service by design.
|
|
@@ -811,7 +823,12 @@ export function OxyServicesUtilityMixin(Base) {
|
|
|
811
823
|
});
|
|
812
824
|
return;
|
|
813
825
|
}
|
|
814
|
-
|
|
826
|
+
const appHasScope = appScopes.includes(scope);
|
|
827
|
+
const delegationHasScope = delegatedScopes.includes(scope);
|
|
828
|
+
const hasRequiredScope = req.serviceActingAs
|
|
829
|
+
? appHasScope && delegationHasScope
|
|
830
|
+
: appHasScope;
|
|
831
|
+
if (hasRequiredScope) {
|
|
815
832
|
next();
|
|
816
833
|
return;
|
|
817
834
|
}
|
|
@@ -869,6 +886,18 @@ async function verifyServiceTokenSignature(token, secret) {
|
|
|
869
886
|
* access token signed by the same shared secret could be replayed as a
|
|
870
887
|
* service token because no claim binding existed.
|
|
871
888
|
*/
|
|
889
|
+
/**
|
|
890
|
+
* Resolve the canonical user id from a validated session's user object.
|
|
891
|
+
*
|
|
892
|
+
* The API serializer emits `id`, but some upstream shapes carry the raw Mongo
|
|
893
|
+
* `_id` instead. We accept either, but only a non-empty string — anything else
|
|
894
|
+
* means the validated identity is unusable and the caller must reject.
|
|
895
|
+
*/
|
|
896
|
+
function getUserIdentityId(user) {
|
|
897
|
+
const candidate = user.id
|
|
898
|
+
?? user._id;
|
|
899
|
+
return typeof candidate === 'string' && candidate.length > 0 ? candidate : null;
|
|
900
|
+
}
|
|
872
901
|
function verifyServiceTokenClaims(decoded, expected) {
|
|
873
902
|
if (decoded.type !== 'service') {
|
|
874
903
|
throw new ServiceTokenClaimError(`Service token has unexpected type '${String(decoded.type)}'`);
|
package/dist/esm/mixins/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { OxyServicesSilentAuthMixin } from './OxyServices.silent.js';
|
|
|
11
11
|
import { OxyServicesRedirectAuthMixin } from './OxyServices.redirect.js';
|
|
12
12
|
import { OxyServicesSsoMixin } from './OxyServices.sso.js';
|
|
13
13
|
import { OxyServicesUserMixin } from './OxyServices.user.js';
|
|
14
|
+
import { OxyServicesIdentityMixin } from './OxyServices.identity.js';
|
|
14
15
|
import { OxyServicesPrivacyMixin } from './OxyServices.privacy.js';
|
|
15
16
|
import { OxyServicesLanguageMixin } from './OxyServices.language.js';
|
|
16
17
|
import { OxyServicesPaymentMixin } from './OxyServices.payment.js';
|
|
@@ -54,6 +55,8 @@ const MIXIN_PIPELINE = [
|
|
|
54
55
|
OxyServicesSsoMixin,
|
|
55
56
|
// User management (requires auth)
|
|
56
57
|
OxyServicesUserMixin,
|
|
58
|
+
// Self-sovereign identity (DID, signed records, auth-method ↔ VM mapping)
|
|
59
|
+
OxyServicesIdentityMixin,
|
|
57
60
|
OxyServicesPrivacyMixin,
|
|
58
61
|
// Feature mixins
|
|
59
62
|
OxyServicesLanguageMixin,
|
package/dist/esm/server/cors.js
CHANGED
|
@@ -11,10 +11,9 @@
|
|
|
11
11
|
*
|
|
12
12
|
* `createOxyCors` returns a self-contained Express middleware (no `cors`
|
|
13
13
|
* package dependency) that:
|
|
14
|
-
* - allows the Oxy apex origin family
|
|
15
|
-
*
|
|
16
|
-
* `console.oxy.so`, `inbox.oxy.so`,
|
|
17
|
-
* central-origin constants already in core, NOT a fresh hardcoded list,
|
|
14
|
+
* - allows the Oxy apex origin family over HTTPS only: the apex plus
|
|
15
|
+
* one-label subdomains such as `auth.oxy.so`, `api.oxy.so`,
|
|
16
|
+
* `accounts.oxy.so`, `console.oxy.so`, and `inbox.oxy.so`,
|
|
18
17
|
* - allows the caller's explicit `appOrigins`,
|
|
19
18
|
* - DENIES everything else (no reflection, never a wildcard with credentials),
|
|
20
19
|
* - echoes back the EXACT matched origin (so credentialed requests work) and
|
|
@@ -24,7 +23,6 @@
|
|
|
24
23
|
* Node/Express-only: exported solely from `@oxyhq/core/server`.
|
|
25
24
|
*/
|
|
26
25
|
import { CENTRAL_IDP_APEX } from '../utils/authWebUrl.js';
|
|
27
|
-
import { registrableApex } from '../utils/fapiAutoDetect.js';
|
|
28
26
|
/** Default HTTP methods allowed across origins. */
|
|
29
27
|
const DEFAULT_ALLOWED_METHODS = ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE', 'OPTIONS'];
|
|
30
28
|
/** Default request headers a browser may send on a credentialed cross-origin call. */
|
|
@@ -38,29 +36,30 @@ const DEFAULT_ALLOWED_HEADERS = [
|
|
|
38
36
|
];
|
|
39
37
|
/** How long (seconds) a browser may cache a successful preflight. */
|
|
40
38
|
const DEFAULT_MAX_AGE_SECONDS = 86400;
|
|
39
|
+
const OXY_ONE_LABEL_SUBDOMAIN_PATTERN = new RegExp(`^[a-z0-9-]+\\.${CENTRAL_IDP_APEX.replace('.', '\\.')}$`);
|
|
41
40
|
/**
|
|
42
|
-
* Whether `candidate` belongs to the Oxy apex origin family
|
|
43
|
-
*
|
|
44
|
-
* apex itself (`https://oxy.so`)
|
|
45
|
-
* `https://api.oxy.so`, …)
|
|
46
|
-
*
|
|
41
|
+
* Whether `candidate` belongs to the built-in Oxy apex origin family. This
|
|
42
|
+
* intentionally mirrors the API allowlist shape: HTTPS only, no custom port,
|
|
43
|
+
* the apex itself (`https://oxy.so`), or exactly one lowercase subdomain label
|
|
44
|
+
* (`https://auth.oxy.so`, `https://api.oxy.so`, …).
|
|
45
|
+
*
|
|
46
|
+
* Arbitrary/multi-level subdomains and `http://*.oxy.so` are not implicitly
|
|
47
|
+
* trusted for credentialed CORS. If a service needs a non-standard development
|
|
48
|
+
* or tenant origin, it must opt in explicitly via `appOrigins`.
|
|
47
49
|
*/
|
|
48
50
|
function isOxyFamilyOrigin(candidate) {
|
|
49
|
-
let hostname;
|
|
50
|
-
let protocol;
|
|
51
51
|
try {
|
|
52
52
|
const url = new URL(candidate);
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
if (url.protocol !== 'https:' || url.port !== '')
|
|
54
|
+
return false;
|
|
55
|
+
const hostname = url.hostname;
|
|
56
|
+
if (hostname === CENTRAL_IDP_APEX)
|
|
57
|
+
return true;
|
|
58
|
+
return OXY_ONE_LABEL_SUBDOMAIN_PATTERN.test(hostname);
|
|
55
59
|
}
|
|
56
60
|
catch {
|
|
57
61
|
return false;
|
|
58
62
|
}
|
|
59
|
-
if (protocol !== 'https:' && protocol !== 'http:')
|
|
60
|
-
return false;
|
|
61
|
-
if (hostname === CENTRAL_IDP_APEX)
|
|
62
|
-
return true;
|
|
63
|
-
return registrableApex(hostname) === CENTRAL_IDP_APEX;
|
|
64
63
|
}
|
|
65
64
|
/** Normalize a raw origin string to its canonical `scheme://host[:port]` form. */
|
|
66
65
|
function normalizeOrigin(raw) {
|
|
@@ -72,8 +71,8 @@ function normalizeOrigin(raw) {
|
|
|
72
71
|
}
|
|
73
72
|
}
|
|
74
73
|
/**
|
|
75
|
-
* Build the origin-matching predicate: true iff `origin` is in the
|
|
76
|
-
* family OR exactly matches one of the configured app origins.
|
|
74
|
+
* Build the origin-matching predicate: true iff `origin` is in the built-in
|
|
75
|
+
* HTTPS Oxy apex family OR exactly matches one of the configured app origins.
|
|
77
76
|
*/
|
|
78
77
|
function buildOriginAllowed(appOrigins) {
|
|
79
78
|
const explicit = new Set();
|
|
@@ -19,12 +19,36 @@ function isBuiltInExempt(req) {
|
|
|
19
19
|
function ipKeyGenerator(ip) {
|
|
20
20
|
return ip.replace(/:/g, '_');
|
|
21
21
|
}
|
|
22
|
-
/**
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Resolve the trusted authenticated rate-limit key.
|
|
24
|
+
*
|
|
25
|
+
* `oxy.auth({ optional: true })` preserves legacy non-session user tokens by
|
|
26
|
+
* decoding their JWT claims locally. Those claims are not cryptographically
|
|
27
|
+
* verified and therefore MUST NOT influence abuse-control buckets. Only use
|
|
28
|
+
* identities that came from a server-validated session or a verified service
|
|
29
|
+
* token/delegation.
|
|
30
|
+
*/
|
|
31
|
+
function resolveTrustedAuthenticatedKey(req) {
|
|
24
32
|
const userId = req.userId ?? req.user?.id ?? req.user?._id;
|
|
25
|
-
if (userId) {
|
|
33
|
+
if (userId && req.sessionId) {
|
|
26
34
|
return `user:${userId}`;
|
|
27
35
|
}
|
|
36
|
+
const delegatedUserId = req.serviceActingAs?.userId;
|
|
37
|
+
if (delegatedUserId && req.serviceApp?.appId) {
|
|
38
|
+
return `user:${delegatedUserId}`;
|
|
39
|
+
}
|
|
40
|
+
const serviceAppId = req.serviceApp?.appId;
|
|
41
|
+
if (serviceAppId) {
|
|
42
|
+
return `service:${serviceAppId}`;
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
/** Resolve the rate-limit key: per trusted authenticated identity, else per (IPv6-safe) IP. */
|
|
47
|
+
function resolveKey(req) {
|
|
48
|
+
const authenticatedKey = resolveTrustedAuthenticatedKey(req);
|
|
49
|
+
if (authenticatedKey) {
|
|
50
|
+
return authenticatedKey;
|
|
51
|
+
}
|
|
28
52
|
const ip = req.ip || req.socket.remoteAddress || 'unknown';
|
|
29
53
|
return ipKeyGenerator(ip);
|
|
30
54
|
}
|
|
@@ -41,9 +65,9 @@ export function createOxyRateLimit(oxy, options = {}) {
|
|
|
41
65
|
windowMs,
|
|
42
66
|
...(store ? { store } : {}),
|
|
43
67
|
max: (req) => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
68
|
+
return resolveTrustedAuthenticatedKey(req)
|
|
69
|
+
? authenticatedMax
|
|
70
|
+
: anonymousMax;
|
|
47
71
|
},
|
|
48
72
|
keyGenerator: (req) => resolveKey(req),
|
|
49
73
|
message,
|
|
@@ -61,8 +85,8 @@ export function createOxyRateLimit(oxy, options = {}) {
|
|
|
61
85
|
resolveSession(req, res, (err) => {
|
|
62
86
|
if (err) {
|
|
63
87
|
// Optional auth never rejects; a token error just means "anonymous".
|
|
64
|
-
// Swallow the error and continue
|
|
65
|
-
next
|
|
88
|
+
// Swallow the error and continue through the anonymous limiter.
|
|
89
|
+
limiter(req, res, next);
|
|
66
90
|
return;
|
|
67
91
|
}
|
|
68
92
|
limiter(req, res, next);
|
|
@@ -20,10 +20,9 @@
|
|
|
20
20
|
* - SSR / non-browser (no `window`).
|
|
21
21
|
* - `localhost`, `127.0.0.1`, IPv4/IPv6 literals.
|
|
22
22
|
* - Hostnames with fewer than two labels.
|
|
23
|
-
* - Hostnames
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* registrable domain.
|
|
23
|
+
* - Hostnames where a registrable domain cannot be determined from the
|
|
24
|
+
* Public Suffix List, including private hosted suffixes such as
|
|
25
|
+
* `github.io`, `pages.dev`, and `netlify.app`.
|
|
27
26
|
*
|
|
28
27
|
* When the page is already loaded ON the IdP itself (`auth.<anything>`),
|
|
29
28
|
* the helper returns the current origin so the SDK keeps everything
|
|
@@ -34,33 +33,10 @@
|
|
|
34
33
|
* (`packages/auth/server/index.ts`), so an honest CNAME pair is all that
|
|
35
34
|
* is required for end-to-end FedCM correctness — no per-RP config.
|
|
36
35
|
*/
|
|
36
|
+
import { getDomain } from 'tldts';
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* of these would yield an attacker-registrable suffix (e.g. `auth.co.uk`),
|
|
41
|
-
* so we bail out instead.
|
|
42
|
-
*
|
|
43
|
-
* This is intentionally a small, explicit allow-list rather than the full
|
|
44
|
-
* Public Suffix List — it covers the suffixes the Oxy ecosystem's RPs use.
|
|
45
|
-
* Any multi-part-TLD RP MUST extend this set (or wire in a proper PSL check)
|
|
46
|
-
* before relying on this helper, otherwise auto-detection silently bails to
|
|
47
|
-
* `undefined` and the consumer must pass `authWebUrl` explicitly.
|
|
48
|
-
*/
|
|
49
|
-
export const MULTIPART_TLDS = new Set([
|
|
50
|
-
'co.uk',
|
|
51
|
-
'com.au',
|
|
52
|
-
'co.jp',
|
|
53
|
-
'co.nz',
|
|
54
|
-
'com.br',
|
|
55
|
-
'co.za',
|
|
56
|
-
'com.mx',
|
|
57
|
-
'co.in',
|
|
58
|
-
'co.kr',
|
|
59
|
-
'com.sg',
|
|
60
|
-
]);
|
|
61
|
-
/**
|
|
62
|
-
* Compute the bare registrable apex (eTLD+1) of a hostname, guarding against
|
|
63
|
-
* multi-part public suffixes.
|
|
38
|
+
* Compute the bare registrable apex (eTLD+1) of a hostname using the Public
|
|
39
|
+
* Suffix List, including private hosted suffixes.
|
|
64
40
|
*
|
|
65
41
|
* This is the pure host-handling kernel shared by {@link autoDetectAuthWebUrl}
|
|
66
42
|
* and the IdP worker — it performs NO protocol handling, NO `auth.` prefixing,
|
|
@@ -72,10 +48,7 @@ export const MULTIPART_TLDS = new Set([
|
|
|
72
48
|
* - IPv4 literals (`192.168.1.10`);
|
|
73
49
|
* - IPv6 literals or any host carrying a port (`[::1]`, anything with `:`);
|
|
74
50
|
* - single-label hosts (`intranet`, `localhost`);
|
|
75
|
-
* -
|
|
76
|
-
* (e.g. `foo.co.uk`), where `labels.slice(-2)` would yield an
|
|
77
|
-
* attacker-registrable suffix (`co.uk`) rather than a real registrable
|
|
78
|
-
* domain. Such hosts MUST configure `authWebUrl` explicitly.
|
|
51
|
+
* - public suffixes without a registrable label (e.g. `co.uk`, `github.io`).
|
|
79
52
|
*
|
|
80
53
|
* @param hostname - A bare hostname (no scheme), e.g. `www.mention.earth`.
|
|
81
54
|
* @returns The eTLD+1 (`mention.earth`), or `null` when undefinable.
|
|
@@ -90,13 +63,11 @@ export function registrableApex(hostname) {
|
|
|
90
63
|
// yields a registrable apex.
|
|
91
64
|
if (host.startsWith('[') || host.includes(':'))
|
|
92
65
|
return null;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return null;
|
|
99
|
-
return lastTwo;
|
|
66
|
+
// The Public Suffix List (private suffixes included) is the source of truth
|
|
67
|
+
// for what an attacker can register. A multi-part public suffix like `co.uk`
|
|
68
|
+
// or a hosted suffix like `github.io` returns no registrable domain, so
|
|
69
|
+
// deriving `auth.<apex>` against it is impossible — `getDomain` returns null.
|
|
70
|
+
return getDomain(host, { allowPrivateDomains: true });
|
|
100
71
|
}
|
|
101
72
|
export function autoDetectAuthWebUrl(location = typeof window !== 'undefined' ? window.location : undefined) {
|
|
102
73
|
if (!location)
|
|
@@ -237,7 +237,7 @@ export async function consumeSsoReturn(oxy, deps = {}) {
|
|
|
237
237
|
}
|
|
238
238
|
let session;
|
|
239
239
|
try {
|
|
240
|
-
session = await oxy.exchangeSsoCode(ret.code);
|
|
240
|
+
session = await oxy.exchangeSsoCode(ret.code, ret.state);
|
|
241
241
|
}
|
|
242
242
|
catch (error) {
|
|
243
243
|
onExchangeError?.(error);
|