@oxyhq/core 9.1.0 → 9.2.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.
- package/README.md +1 -1
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/boot/{coldBootV2.js → sessionColdBoot.js} +15 -2
- package/dist/cjs/index.js +27 -6
- package/dist/cjs/mixins/OxyServices.accounts.js +3 -0
- package/dist/cjs/mixins/OxyServices.auth.js +61 -0
- package/dist/cjs/mixins/OxyServices.deviceBoot.js +29 -1
- package/dist/cjs/server/index.js +8 -1
- package/dist/cjs/session/SessionClient.js +57 -25
- package/dist/cjs/session/accountDialogController.js +21 -9
- package/dist/cjs/session/authStateStore.js +13 -7
- package/dist/cjs/session/hubSync.js +55 -0
- package/dist/cjs/session/sessionClientHost.js +5 -0
- package/dist/cjs/utils/coldBoot.js +1 -1
- package/dist/cjs/utils/oauthPkce.js +65 -1
- package/dist/cjs/utils/officialOrigins.js +128 -0
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/boot/{coldBootV2.js → sessionColdBoot.js} +15 -2
- package/dist/esm/index.js +5 -2
- package/dist/esm/mixins/OxyServices.accounts.js +1 -0
- package/dist/esm/mixins/OxyServices.auth.js +61 -0
- package/dist/esm/mixins/OxyServices.deviceBoot.js +30 -2
- package/dist/esm/server/index.js +1 -0
- package/dist/esm/session/SessionClient.js +57 -25
- package/dist/esm/session/accountDialogController.js +21 -9
- package/dist/esm/session/authStateStore.js +13 -7
- package/dist/esm/session/hubSync.js +51 -0
- package/dist/esm/session/sessionClientHost.js +5 -0
- package/dist/esm/utils/coldBoot.js +1 -1
- package/dist/esm/utils/oauthPkce.js +60 -0
- package/dist/esm/utils/officialOrigins.js +119 -0
- package/dist/types/.tsbuildinfo +1 -1
- package/dist/types/boot/{coldBootV2.d.ts → sessionColdBoot.d.ts} +1 -1
- package/dist/types/index.d.ts +9 -5
- package/dist/types/mixins/OxyServices.accounts.d.ts +8 -0
- package/dist/types/mixins/OxyServices.auth.d.ts +15 -0
- package/dist/types/mixins/OxyServices.deviceBoot.d.ts +6 -2
- package/dist/types/models/interfaces.d.ts +3 -1
- package/dist/types/models/session.d.ts +6 -0
- package/dist/types/server/index.d.ts +1 -0
- package/dist/types/session/SessionClient.d.ts +6 -0
- package/dist/types/session/accountDialogController.d.ts +9 -1
- package/dist/types/session/authStateStore.d.ts +1 -1
- package/dist/types/session/hubSync.d.ts +20 -0
- package/dist/types/session/sessionClientHost.d.ts +2 -1
- package/dist/types/utils/coldBoot.d.ts +2 -2
- package/dist/types/utils/oauthPkce.d.ts +27 -0
- package/dist/types/utils/officialOrigins.d.ts +17 -0
- package/package.json +2 -2
- package/src/boot/__tests__/{coldBootV2.test.ts → sessionColdBoot.test.ts} +21 -1
- package/src/boot/{coldBootV2.ts → sessionColdBoot.ts} +15 -2
- package/src/index.ts +30 -3
- package/src/mixins/OxyServices.accounts.ts +9 -0
- package/src/mixins/OxyServices.auth.ts +72 -1
- package/src/mixins/OxyServices.deviceBoot.ts +46 -1
- package/src/models/interfaces.ts +3 -1
- package/src/models/session.ts +6 -0
- package/src/server/index.ts +8 -0
- package/src/session/SessionClient.ts +68 -26
- package/src/session/__tests__/SessionClient.additive.test.ts +1 -0
- package/src/session/__tests__/SessionClient.broadcastChannel.test.ts +1 -0
- package/src/session/__tests__/SessionClient.diagnostics.test.ts +1 -0
- package/src/session/__tests__/SessionClient.rest.test.ts +1 -0
- package/src/session/__tests__/SessionClient.socket.test.ts +1 -0
- package/src/session/__tests__/SessionClient.socketFactory.test.ts +1 -0
- package/src/session/__tests__/SessionClient.state.test.ts +1 -0
- package/src/session/__tests__/accountDialogController.test.ts +46 -7
- package/src/session/__tests__/sessionIntegration.test.ts +7 -0
- package/src/session/accountDialogController.ts +45 -11
- package/src/session/authStateStore.ts +18 -9
- package/src/session/hubSync.ts +79 -0
- package/src/session/sessionClientHost.ts +10 -2
- package/src/utils/__tests__/officialOrigins.test.ts +74 -0
- package/src/utils/coldBoot.ts +2 -2
- package/src/utils/oauthPkce.ts +71 -0
- package/src/utils/officialOrigins.ts +124 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runSessionColdBoot = runSessionColdBoot;
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Device-first session cold boot for every consumer.
|
|
6
6
|
*
|
|
7
7
|
* On a fresh page load / app launch this resolves the device's session in a
|
|
8
8
|
* deterministic order, built on the pure `runColdBoot` primitive. It NEVER
|
|
@@ -95,7 +95,7 @@ async function runSessionColdBoot(opts) {
|
|
|
95
95
|
return { kind: 'skip' };
|
|
96
96
|
}
|
|
97
97
|
// Transient (network / 5xx): keep the secret; a later attempt can succeed.
|
|
98
|
-
loggerUtils_1.logger.debug('device-secret mint failed (transient) — keeping secret', { component: '
|
|
98
|
+
loggerUtils_1.logger.debug('device-secret mint failed (transient) — keeping secret', { component: 'sessionColdBoot', method: 'device-secret-mint' }, error);
|
|
99
99
|
return { kind: 'skip' };
|
|
100
100
|
}
|
|
101
101
|
},
|
|
@@ -109,6 +109,19 @@ async function runSessionColdBoot(opts) {
|
|
|
109
109
|
if (!session?.accessToken) {
|
|
110
110
|
return { kind: 'skip' };
|
|
111
111
|
}
|
|
112
|
+
// `verifyChallenge` mints a rotating deviceSecret; persist it so the next
|
|
113
|
+
// boot can use the faster device-secret-mint lane (sockets + tab-focus
|
|
114
|
+
// re-mint depend on the credential being in the store).
|
|
115
|
+
if (session.deviceId && session.deviceSecret) {
|
|
116
|
+
await store.save({
|
|
117
|
+
sessionId: session.sessionId,
|
|
118
|
+
userId: session.user.id,
|
|
119
|
+
deviceId: session.deviceId,
|
|
120
|
+
deviceSecret: session.deviceSecret,
|
|
121
|
+
accessToken: session.accessToken,
|
|
122
|
+
expiresAt: session.expiresAt,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
112
125
|
return {
|
|
113
126
|
kind: 'session',
|
|
114
127
|
session: {
|
package/dist/cjs/index.js
CHANGED
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
* If a symbol does not appear here, it is NOT part of the public API.
|
|
19
19
|
*/
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.
|
|
22
|
-
exports.
|
|
23
|
-
exports.
|
|
24
|
-
exports.packageInfo = exports.runSessionColdBoot = exports.TOKEN_REFRESH_LEAD_MS = exports.startTokenRefreshScheduler = exports.installAuthRefreshHandler = exports.createAuthRefreshHandler = exports.refreshPersistedSession = exports.AUTH_STATE_STORAGE_KEY = exports.createMemoryAuthStateStore = exports.createNativeAuthStateStore = exports.createWebAuthStateStore = exports.createAccountDialogController = void 0;
|
|
21
|
+
exports.darkenColor = exports.isWebBrowser = exports.isAndroid = exports.isIOS = exports.isNative = exports.isWeb = exports.setPlatformOS = exports.getPlatformOS = exports.isRTLLocale = exports.normalizeLanguageCode = exports.getNativeLanguageName = exports.getLanguageName = exports.getLanguageMetadata = exports.SUPPORTED_LANGUAGES = exports.TopicSource = exports.TopicType = exports.SECURITY_EVENT_SEVERITY_MAP = exports.DeviceManager = exports.RecoveryPhraseService = exports.SignatureService = exports.IdentityPersistError = exports.IdentityAlreadyExistsError = exports.KeyManager = exports.sessionsArraysEqual = exports.normalizeAndSortSessions = exports.mergeSessions = exports.authenticatedApiCall = exports.withAuthErrorHandling = exports.isAuthenticationError = exports.ensureValidToken = exports.AuthenticationFailedError = exports.SessionSyncRequiredError = exports.verifyPublicCardAttestation = exports.parseAttestPayload = exports.parseIdPayload = exports.buildUserDid = exports.ORGANIZATION_CATEGORIES = exports.normalizeProfileLinks = exports.getNormalizedUserHandle = exports.getCanonicalUserHandle = exports.normalizeUserIdentityOrNull = exports.normalizeUserIdentity = exports.getNormalizedUserId = exports.OxyAppDataIdentifierError = exports.ServiceCredentialMismatchError = exports.oxyClient = exports.OXY_CLOUD_URL = exports.OxyAuthenticationTimeoutError = exports.OxyAuthenticationError = exports.OxyServices = void 0;
|
|
22
|
+
exports.isValidUsername = exports.isValidEmail = exports.PASSWORD_REGEX = exports.USERNAME_REGEX = exports.EMAIL_REGEX = exports.retryAsync = exports.validateRequiredFields = exports.handleHttpError = exports.createApiError = exports.ErrorCodes = exports.safeJsonParse = exports.buildPaginationParams = exports.buildUrl = exports.buildSearchParams = exports.translate = exports.createDebugLogger = exports.debugError = exports.debugWarn = exports.debugLog = exports.isDev = exports.withRetry = exports.delay = exports.shouldAllowRequest = exports.recordSuccess = exports.recordFailure = exports.calculateBackoffInterval = exports.createCircuitBreakerState = exports.DEFAULT_CIRCUIT_BREAKER_CONFIG = exports.isRetryableError = exports.isNetworkError = exports.isServerError = exports.isRateLimitError = exports.isNotFoundError = exports.isForbiddenError = exports.isUnauthorizedError = exports.isAlreadyRegisteredError = exports.getErrorMessage = exports.getErrorStatus = exports.HttpStatus = exports.getSystemColorScheme = exports.systemPrefersDarkMode = exports.getOppositeTheme = exports.normalizeColorScheme = exports.normalizeTheme = exports.getContrastTextColor = exports.isLightColor = exports.withOpacity = exports.rgbToHex = exports.hexToRgb = exports.lightenColor = void 0;
|
|
23
|
+
exports.buildIdpHubOrigin = exports.clearOAuthHandshake = exports.readOAuthHandshake = exports.persistOAuthHandshake = exports.normalizeOAuthRedirectUri = exports.OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY = exports.OXY_SILENT_OAUTH_ATTEMPTED_KEY = exports.OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY = exports.OXY_OAUTH_STATE_STORAGE_KEY = exports.OXY_AUTHORIZE_URL = exports.DEFAULT_OAUTH_SCOPE = exports.generatePkcePair = exports.generateOAuthState = exports.computeCodeChallenge = exports.buildOAuthAuthorizeUrl = exports.runColdBoot = exports.CENTRAL_IDP_APEX = exports.registrableApex = exports.getAccountColor = exports.formatPublicKeyHandle = exports.getAccountFallbackHandle = exports.getAccountDisplayName = exports.createQuickAccount = exports.buildAccountsArray = exports.updateAvatarVisibility = exports.logPerformance = exports.logPayment = exports.logDevice = exports.logUser = exports.logSession = exports.logApi = exports.logAuth = exports.LogLevel = exports.logger = exports.validateAndSanitizeUserInput = exports.isValidObjectId = exports.sanitizeHTML = exports.sanitizeString = exports.isValidFileType = exports.isValidFileSize = exports.isValidDate = exports.isValidURL = exports.isValidUUID = exports.isValidObject = exports.isValidArray = exports.isRequiredBoolean = exports.isRequiredNumber = exports.isRequiredString = exports.isValidDisplayName = exports.isValidPassword = void 0;
|
|
24
|
+
exports.packageInfo = exports.runSessionColdBoot = exports.TOKEN_REFRESH_LEAD_MS = exports.startTokenRefreshScheduler = exports.installAuthRefreshHandler = exports.createAuthRefreshHandler = exports.refreshPersistedSession = exports.AUTH_STATE_STORAGE_KEY = exports.createMemoryAuthStateStore = exports.createNativeAuthStateStore = exports.createWebAuthStateStore = exports.createAccountDialogController = exports.AccountDialogController = exports.switchableAccountIds = exports.projectSwitchableAccounts = exports.accountIdsOf = exports.activeUserOf = exports.activeSessionIdOf = exports.deviceStateToClientSessions = exports.createSessionClient = exports.createSessionClientHost = exports.SessionClient = exports.redeemHubTicketOnHub = exports.syncHubAfterSignIn = exports.parseHubSyncReturnUrl = exports.normalizeOfficialReturnOrigin = exports.isAllowedDeviceJoinOrigin = exports.isOfficialWebOrigin = exports.isIdpHubOrigin = exports.buildHubSyncUrl = void 0;
|
|
25
25
|
// Ensure crypto polyfills are loaded before anything else
|
|
26
26
|
require("./crypto/polyfill");
|
|
27
27
|
// ---------------------------------------------------------------------------
|
|
@@ -53,6 +53,8 @@ Object.defineProperty(exports, "getCanonicalUserHandle", { enumerable: true, get
|
|
|
53
53
|
Object.defineProperty(exports, "getNormalizedUserHandle", { enumerable: true, get: function () { return userHandle_1.getNormalizedUserHandle; } });
|
|
54
54
|
var profileLinks_1 = require("./utils/profileLinks");
|
|
55
55
|
Object.defineProperty(exports, "normalizeProfileLinks", { enumerable: true, get: function () { return profileLinks_1.normalizeProfileLinks; } });
|
|
56
|
+
var OxyServices_accounts_1 = require("./mixins/OxyServices.accounts");
|
|
57
|
+
Object.defineProperty(exports, "ORGANIZATION_CATEGORIES", { enumerable: true, get: function () { return OxyServices_accounts_1.ORGANIZATION_CATEGORIES; } });
|
|
56
58
|
// ---------------------------------------------------------------------------
|
|
57
59
|
// Self-sovereign identity (DID, signed records, auth-method ↔ VM mapping,
|
|
58
60
|
// verified domains). Wire shapes (DidDocument, SignedRecordEnvelope,
|
|
@@ -278,6 +280,25 @@ Object.defineProperty(exports, "generateOAuthState", { enumerable: true, get: fu
|
|
|
278
280
|
Object.defineProperty(exports, "generatePkcePair", { enumerable: true, get: function () { return oauthPkce_1.generatePkcePair; } });
|
|
279
281
|
Object.defineProperty(exports, "DEFAULT_OAUTH_SCOPE", { enumerable: true, get: function () { return oauthPkce_1.DEFAULT_OAUTH_SCOPE; } });
|
|
280
282
|
Object.defineProperty(exports, "OXY_AUTHORIZE_URL", { enumerable: true, get: function () { return oauthPkce_1.OXY_AUTHORIZE_URL; } });
|
|
283
|
+
Object.defineProperty(exports, "OXY_OAUTH_STATE_STORAGE_KEY", { enumerable: true, get: function () { return oauthPkce_1.OXY_OAUTH_STATE_STORAGE_KEY; } });
|
|
284
|
+
Object.defineProperty(exports, "OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY", { enumerable: true, get: function () { return oauthPkce_1.OXY_OAUTH_CODE_VERIFIER_STORAGE_KEY; } });
|
|
285
|
+
Object.defineProperty(exports, "OXY_SILENT_OAUTH_ATTEMPTED_KEY", { enumerable: true, get: function () { return oauthPkce_1.OXY_SILENT_OAUTH_ATTEMPTED_KEY; } });
|
|
286
|
+
Object.defineProperty(exports, "OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY", { enumerable: true, get: function () { return oauthPkce_1.OXY_CROSS_ORIGIN_RESTORE_ATTEMPTED_KEY; } });
|
|
287
|
+
Object.defineProperty(exports, "normalizeOAuthRedirectUri", { enumerable: true, get: function () { return oauthPkce_1.normalizeOAuthRedirectUri; } });
|
|
288
|
+
Object.defineProperty(exports, "persistOAuthHandshake", { enumerable: true, get: function () { return oauthPkce_1.persistOAuthHandshake; } });
|
|
289
|
+
Object.defineProperty(exports, "readOAuthHandshake", { enumerable: true, get: function () { return oauthPkce_1.readOAuthHandshake; } });
|
|
290
|
+
Object.defineProperty(exports, "clearOAuthHandshake", { enumerable: true, get: function () { return oauthPkce_1.clearOAuthHandshake; } });
|
|
291
|
+
var officialOrigins_1 = require("./utils/officialOrigins");
|
|
292
|
+
Object.defineProperty(exports, "buildIdpHubOrigin", { enumerable: true, get: function () { return officialOrigins_1.buildIdpHubOrigin; } });
|
|
293
|
+
Object.defineProperty(exports, "buildHubSyncUrl", { enumerable: true, get: function () { return officialOrigins_1.buildHubSyncUrl; } });
|
|
294
|
+
Object.defineProperty(exports, "isIdpHubOrigin", { enumerable: true, get: function () { return officialOrigins_1.isIdpHubOrigin; } });
|
|
295
|
+
Object.defineProperty(exports, "isOfficialWebOrigin", { enumerable: true, get: function () { return officialOrigins_1.isOfficialWebOrigin; } });
|
|
296
|
+
Object.defineProperty(exports, "isAllowedDeviceJoinOrigin", { enumerable: true, get: function () { return officialOrigins_1.isAllowedDeviceJoinOrigin; } });
|
|
297
|
+
Object.defineProperty(exports, "normalizeOfficialReturnOrigin", { enumerable: true, get: function () { return officialOrigins_1.normalizeOfficialReturnOrigin; } });
|
|
298
|
+
Object.defineProperty(exports, "parseHubSyncReturnUrl", { enumerable: true, get: function () { return officialOrigins_1.parseHubSyncReturnUrl; } });
|
|
299
|
+
var hubSync_1 = require("./session/hubSync");
|
|
300
|
+
Object.defineProperty(exports, "syncHubAfterSignIn", { enumerable: true, get: function () { return hubSync_1.syncHubAfterSignIn; } });
|
|
301
|
+
Object.defineProperty(exports, "redeemHubTicketOnHub", { enumerable: true, get: function () { return hubSync_1.redeemHubTicketOnHub; } });
|
|
281
302
|
// ---------------------------------------------------------------------------
|
|
282
303
|
// Session sync (device-scoped multi-account session client)
|
|
283
304
|
// ---------------------------------------------------------------------------
|
|
@@ -330,8 +351,8 @@ Object.defineProperty(exports, "createAuthRefreshHandler", { enumerable: true, g
|
|
|
330
351
|
Object.defineProperty(exports, "installAuthRefreshHandler", { enumerable: true, get: function () { return refresh_1.installAuthRefreshHandler; } });
|
|
331
352
|
Object.defineProperty(exports, "startTokenRefreshScheduler", { enumerable: true, get: function () { return refresh_1.startTokenRefreshScheduler; } });
|
|
332
353
|
Object.defineProperty(exports, "TOKEN_REFRESH_LEAD_MS", { enumerable: true, get: function () { return refresh_1.TOKEN_REFRESH_LEAD_MS; } });
|
|
333
|
-
var
|
|
334
|
-
Object.defineProperty(exports, "runSessionColdBoot", { enumerable: true, get: function () { return
|
|
354
|
+
var sessionColdBoot_1 = require("./boot/sessionColdBoot");
|
|
355
|
+
Object.defineProperty(exports, "runSessionColdBoot", { enumerable: true, get: function () { return sessionColdBoot_1.runSessionColdBoot; } });
|
|
335
356
|
// API response contracts (request/response Zod schemas + inferred types) live in
|
|
336
357
|
// `@oxyhq/contracts` — the single source of truth shared by the backend and every
|
|
337
358
|
// client SDK. Import them directly from `@oxyhq/contracts`; `@oxyhq/core` does NOT
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ORGANIZATION_CATEGORIES = void 0;
|
|
3
4
|
exports.OxyServicesAccountsMixin = OxyServicesAccountsMixin;
|
|
4
5
|
const userIdentity_1 = require("../utils/userIdentity");
|
|
5
6
|
const mixinHelpers_1 = require("./mixinHelpers");
|
|
7
|
+
var contracts_1 = require("@oxyhq/contracts");
|
|
8
|
+
Object.defineProperty(exports, "ORGANIZATION_CATEGORIES", { enumerable: true, get: function () { return contracts_1.ORGANIZATION_CATEGORIES; } });
|
|
6
9
|
function OxyServicesAccountsMixin(Base) {
|
|
7
10
|
return class extends Base {
|
|
8
11
|
constructor(...args) {
|
|
@@ -808,6 +808,7 @@ function OxyServicesAuthMixin(Base) {
|
|
|
808
808
|
password,
|
|
809
809
|
deviceName: options.deviceName,
|
|
810
810
|
deviceFingerprint: options.deviceFingerprint,
|
|
811
|
+
...(options.deviceId ? { deviceId: options.deviceId } : {}),
|
|
811
812
|
}, { cache: false });
|
|
812
813
|
const parsed = (0, contracts_1.safeParseContract)(contracts_1.loginResultSchema, res);
|
|
813
814
|
if (!parsed) {
|
|
@@ -836,6 +837,7 @@ function OxyServicesAuthMixin(Base) {
|
|
|
836
837
|
token: params.token,
|
|
837
838
|
backupCode: params.backupCode,
|
|
838
839
|
deviceName: params.deviceName,
|
|
840
|
+
...(params.deviceId ? { deviceId: params.deviceId } : {}),
|
|
839
841
|
}, { cache: false });
|
|
840
842
|
const parsed = (0, contracts_1.safeParseContract)(contracts_1.loginResultSchema, res);
|
|
841
843
|
if (!parsed || 'twoFactorRequired' in parsed) {
|
|
@@ -850,5 +852,64 @@ function OxyServicesAuthMixin(Base) {
|
|
|
850
852
|
throw this.handleError(error);
|
|
851
853
|
}
|
|
852
854
|
}
|
|
855
|
+
/**
|
|
856
|
+
* Exchange an OAuth authorization code (returned to the RP redirect URI
|
|
857
|
+
* after password sign-in at auth.oxy.so) for a device-first session.
|
|
858
|
+
* Public first-party clients use PKCE (`codeVerifier`); the access token is
|
|
859
|
+
* planted immediately on success.
|
|
860
|
+
*/
|
|
861
|
+
async exchangeOAuthCode(params) {
|
|
862
|
+
try {
|
|
863
|
+
const res = await this.makeRequest('POST', '/auth/oauth/token', {
|
|
864
|
+
code: params.code,
|
|
865
|
+
clientId: params.clientId,
|
|
866
|
+
redirectUri: params.redirectUri,
|
|
867
|
+
codeVerifier: params.codeVerifier,
|
|
868
|
+
}, { cache: false });
|
|
869
|
+
const payload = res.data ??
|
|
870
|
+
res;
|
|
871
|
+
if (!payload || typeof payload !== 'object') {
|
|
872
|
+
throw new Error('auth/oauth/token returned an unexpected response shape');
|
|
873
|
+
}
|
|
874
|
+
const record = payload;
|
|
875
|
+
const accessToken = (record.access_token ?? record.accessToken);
|
|
876
|
+
const sessionId = (record.session_id ?? record.sessionId);
|
|
877
|
+
const deviceId = (record.deviceId ?? record.device_id);
|
|
878
|
+
const deviceSecret = (record.deviceSecret ?? record.device_secret);
|
|
879
|
+
const userRaw = record.user;
|
|
880
|
+
if (!sessionId || !deviceId || !userRaw || typeof userRaw !== 'object') {
|
|
881
|
+
throw new Error('auth/oauth/token returned an incomplete session payload');
|
|
882
|
+
}
|
|
883
|
+
const userObj = userRaw;
|
|
884
|
+
const userId = userObj.id;
|
|
885
|
+
if (!userId) {
|
|
886
|
+
throw new Error('auth/oauth/token returned a session without user.id');
|
|
887
|
+
}
|
|
888
|
+
const expiresInSec = typeof record.expires_in === 'number'
|
|
889
|
+
? record.expires_in
|
|
890
|
+
: typeof record.expiresIn === 'number'
|
|
891
|
+
? record.expiresIn
|
|
892
|
+
: 15 * 60;
|
|
893
|
+
const expiresAt = new Date(Date.now() + expiresInSec * 1000).toISOString();
|
|
894
|
+
if (accessToken) {
|
|
895
|
+
this.setTokens(accessToken);
|
|
896
|
+
}
|
|
897
|
+
return {
|
|
898
|
+
sessionId,
|
|
899
|
+
deviceId,
|
|
900
|
+
expiresAt,
|
|
901
|
+
accessToken,
|
|
902
|
+
deviceSecret,
|
|
903
|
+
user: {
|
|
904
|
+
id: userId,
|
|
905
|
+
username: typeof userObj.username === 'string' ? userObj.username : undefined,
|
|
906
|
+
avatar: typeof userObj.avatar === 'string' ? userObj.avatar : undefined,
|
|
907
|
+
},
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
catch (error) {
|
|
911
|
+
throw this.handleError(error);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
853
914
|
};
|
|
854
915
|
}
|
|
@@ -5,7 +5,7 @@ exports.OxyServicesDeviceBootMixin = OxyServicesDeviceBootMixin;
|
|
|
5
5
|
* Device-first token mint mixin.
|
|
6
6
|
*
|
|
7
7
|
* The client half of the zero-cookie device transport: the single network call
|
|
8
|
-
* the cold boot (`
|
|
8
|
+
* the cold boot (`sessionColdBoot`) and the unified re-mint handler (`refresh.ts`)
|
|
9
9
|
* make to turn a first-party `deviceId` + `deviceSecret` into a fresh access
|
|
10
10
|
* token. The response is validated against the `@oxyhq/contracts`
|
|
11
11
|
* `deviceTokenMintResponseSchema`, so producer (oxy-api) and consumer cannot
|
|
@@ -47,5 +47,33 @@ function OxyServicesDeviceBootMixin(Base) {
|
|
|
47
47
|
throw this.handleError(error);
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
+
/** Mint a one-time hub sync ticket (bearer required). */
|
|
51
|
+
async issueHubTicket(returnOrigin) {
|
|
52
|
+
try {
|
|
53
|
+
const res = await this.makeRequest('POST', '/session/device/hub-ticket', { returnOrigin }, { cache: false });
|
|
54
|
+
const parsed = (0, contracts_1.safeParseContract)(contracts_1.deviceHubTicketIssueResponseSchema, res);
|
|
55
|
+
if (!parsed) {
|
|
56
|
+
throw new Error('session/device/hub-ticket returned an unexpected response shape');
|
|
57
|
+
}
|
|
58
|
+
return parsed;
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
throw this.handleError(error);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Redeem a hub sync ticket for a fresh device secret (public). */
|
|
65
|
+
async redeemHubTicket(ticket, returnOrigin) {
|
|
66
|
+
try {
|
|
67
|
+
const res = await this.makeRequest('POST', '/session/device/redeem-ticket', { ticket, returnOrigin }, { cache: false, skipAuth: true });
|
|
68
|
+
const parsed = (0, contracts_1.safeParseContract)(contracts_1.deviceHubTicketRedeemResponseSchema, res);
|
|
69
|
+
if (!parsed) {
|
|
70
|
+
throw new Error('session/device/redeem-ticket returned an unexpected response shape');
|
|
71
|
+
}
|
|
72
|
+
return parsed;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
throw this.handleError(error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
50
78
|
};
|
|
51
79
|
}
|
package/dist/cjs/server/index.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* ```
|
|
17
17
|
*/
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
-
exports.registrableApex = exports.verifySecret = exports.createOxyCors = exports.UPSTREAM_HEADERS_TIMEOUT_MS = exports.MAX_URL_LENGTH = exports.MAX_REDIRECTS = exports.DEFAULT_USER_AGENT = exports.BLOCKED_HOSTNAMES = exports.ALLOWED_PROTOCOLS = exports.ALLOWED_PORTS = exports.UpstreamError = exports.SsrfRejection = exports.safeFetch = exports.isBlockedIp = exports.assertSafePublicUrl = exports.createOxyRateLimit = exports.requireOxyAuth = exports.isOxyAuthenticated = exports.getRequiredOxyUserId = exports.getOxyUserId = exports.createOxyAuthMiddleware = exports.createOptionalOxyAuth = void 0;
|
|
19
|
+
exports.parseHubSyncReturnUrl = exports.normalizeOfficialReturnOrigin = exports.isOfficialWebOrigin = exports.isIdpHubOrigin = exports.buildHubSyncUrl = exports.buildIdpHubOrigin = exports.registrableApex = exports.verifySecret = exports.createOxyCors = exports.UPSTREAM_HEADERS_TIMEOUT_MS = exports.MAX_URL_LENGTH = exports.MAX_REDIRECTS = exports.DEFAULT_USER_AGENT = exports.BLOCKED_HOSTNAMES = exports.ALLOWED_PROTOCOLS = exports.ALLOWED_PORTS = exports.UpstreamError = exports.SsrfRejection = exports.safeFetch = exports.isBlockedIp = exports.assertSafePublicUrl = exports.createOxyRateLimit = exports.requireOxyAuth = exports.isOxyAuthenticated = exports.getRequiredOxyUserId = exports.getOxyUserId = exports.createOxyAuthMiddleware = exports.createOptionalOxyAuth = void 0;
|
|
20
20
|
var auth_1 = require("./auth");
|
|
21
21
|
Object.defineProperty(exports, "createOptionalOxyAuth", { enumerable: true, get: function () { return auth_1.createOptionalOxyAuth; } });
|
|
22
22
|
Object.defineProperty(exports, "createOxyAuthMiddleware", { enumerable: true, get: function () { return auth_1.createOxyAuthMiddleware; } });
|
|
@@ -52,3 +52,10 @@ Object.defineProperty(exports, "verifySecret", { enumerable: true, get: function
|
|
|
52
52
|
// lets `@oxyhq/api` derive `auth.<apex>` without duplicating PSL logic.
|
|
53
53
|
var registrableApex_1 = require("../utils/registrableApex");
|
|
54
54
|
Object.defineProperty(exports, "registrableApex", { enumerable: true, get: function () { return registrableApex_1.registrableApex; } });
|
|
55
|
+
var officialOrigins_1 = require("../utils/officialOrigins");
|
|
56
|
+
Object.defineProperty(exports, "buildIdpHubOrigin", { enumerable: true, get: function () { return officialOrigins_1.buildIdpHubOrigin; } });
|
|
57
|
+
Object.defineProperty(exports, "buildHubSyncUrl", { enumerable: true, get: function () { return officialOrigins_1.buildHubSyncUrl; } });
|
|
58
|
+
Object.defineProperty(exports, "isIdpHubOrigin", { enumerable: true, get: function () { return officialOrigins_1.isIdpHubOrigin; } });
|
|
59
|
+
Object.defineProperty(exports, "isOfficialWebOrigin", { enumerable: true, get: function () { return officialOrigins_1.isOfficialWebOrigin; } });
|
|
60
|
+
Object.defineProperty(exports, "normalizeOfficialReturnOrigin", { enumerable: true, get: function () { return officialOrigins_1.normalizeOfficialReturnOrigin; } });
|
|
61
|
+
Object.defineProperty(exports, "parseHubSyncReturnUrl", { enumerable: true, get: function () { return officialOrigins_1.parseHubSyncReturnUrl; } });
|
|
@@ -63,21 +63,32 @@ class SessionClient {
|
|
|
63
63
|
return false;
|
|
64
64
|
}
|
|
65
65
|
this.state = next;
|
|
66
|
-
this.
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
const transport = this.options.transport;
|
|
67
|
+
const needsMintBeforeNotify = transport != null && next.accounts.length > 0 && !this.host.getAccessToken();
|
|
68
|
+
const finishApply = () => {
|
|
69
|
+
this.notify();
|
|
70
|
+
if (next.accounts.length === 0 && this.options.onUnauthenticated) {
|
|
71
|
+
try {
|
|
72
|
+
this.options.onUnauthenticated();
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
loggerUtils_1.logger.error('[SessionClient] onUnauthenticated threw', error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
if (needsMintBeforeNotify) {
|
|
80
|
+
void transport.ensureActiveToken(next).then(finishApply).catch((error) => {
|
|
69
81
|
loggerUtils_1.logger.warn('[SessionClient] ensureActiveToken failed', { component: 'SessionClient' }, error);
|
|
82
|
+
finishApply();
|
|
70
83
|
});
|
|
71
84
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
catch (error) {
|
|
79
|
-
loggerUtils_1.logger.error('[SessionClient] onUnauthenticated threw', error);
|
|
85
|
+
else {
|
|
86
|
+
if (transport) {
|
|
87
|
+
void transport.ensureActiveToken(next).catch((error) => {
|
|
88
|
+
loggerUtils_1.logger.warn('[SessionClient] ensureActiveToken failed', { component: 'SessionClient' }, error);
|
|
89
|
+
});
|
|
80
90
|
}
|
|
91
|
+
finishApply();
|
|
81
92
|
}
|
|
82
93
|
return true;
|
|
83
94
|
}
|
|
@@ -155,19 +166,24 @@ class SessionClient {
|
|
|
155
166
|
this.started = true;
|
|
156
167
|
this.tokenUnsub = this.host.onTokensChanged((token) => {
|
|
157
168
|
// A rotated/fresh bearer landed — reconnect a dropped socket so its
|
|
158
|
-
// handshake re-runs with the current token. Sign-out (null token)
|
|
159
|
-
//
|
|
160
|
-
if (!
|
|
169
|
+
// handshake re-runs with the current token. Sign-out (null token) keeps
|
|
170
|
+
// the device-scoped socket when a device credential is available.
|
|
171
|
+
if (!this.socket)
|
|
161
172
|
return;
|
|
162
|
-
if (
|
|
173
|
+
if (token) {
|
|
174
|
+
if (!this.socket.connected) {
|
|
175
|
+
this.socket.connect();
|
|
176
|
+
}
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const cred = this.host.getDeviceCredential();
|
|
180
|
+
if (cred && !this.socket.connected) {
|
|
163
181
|
this.socket.connect();
|
|
164
182
|
}
|
|
165
183
|
});
|
|
166
184
|
this.openBroadcastChannel();
|
|
167
|
-
//
|
|
168
|
-
// signed-out
|
|
169
|
-
// failure is non-fatal — the socket still connects so realtime sync
|
|
170
|
-
// survives a transient state-fetch error.
|
|
185
|
+
// Device-scoped socket: bearer when authenticated, else deviceId+deviceSecret so
|
|
186
|
+
// signed-out tabs still receive `session_state` and can mint on change.
|
|
171
187
|
if (this.host.getAccessToken()) {
|
|
172
188
|
try {
|
|
173
189
|
await this.bootstrap();
|
|
@@ -210,15 +226,29 @@ class SessionClient {
|
|
|
210
226
|
}
|
|
211
227
|
if (!this.started)
|
|
212
228
|
return; // stopped while the dynamic import was in flight
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if (!
|
|
229
|
+
const token = this.host.getAccessToken();
|
|
230
|
+
const deviceCredential = this.host.getDeviceCredential();
|
|
231
|
+
if (!token && !deviceCredential)
|
|
216
232
|
return;
|
|
217
233
|
const socket = io(this.host.getBaseURL(), {
|
|
218
234
|
transports: ['websocket'],
|
|
219
235
|
autoConnect: true,
|
|
236
|
+
reconnection: true,
|
|
237
|
+
reconnectionAttempts: Infinity,
|
|
238
|
+
reconnectionDelay: 1000,
|
|
239
|
+
reconnectionDelayMax: 10000,
|
|
220
240
|
auth: (cb) => {
|
|
221
|
-
|
|
241
|
+
const bearer = this.host.getAccessToken();
|
|
242
|
+
if (bearer) {
|
|
243
|
+
cb({ token: bearer });
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const cred = this.host.getDeviceCredential();
|
|
247
|
+
if (cred) {
|
|
248
|
+
cb({ deviceId: cred.deviceId, deviceSecret: cred.deviceSecret });
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
cb({ token: '' });
|
|
222
252
|
},
|
|
223
253
|
});
|
|
224
254
|
socket.on('session_state', (payload) => {
|
|
@@ -226,9 +256,11 @@ class SessionClient {
|
|
|
226
256
|
if (!applied)
|
|
227
257
|
return;
|
|
228
258
|
// A push changed the active account on another device/tab — re-fetch state
|
|
229
|
-
// to plant the access token for the newly-active account.
|
|
259
|
+
// to plant the access token for the newly-active account. When this tab is
|
|
260
|
+
// still signed out, applyState mints via ensureActiveToken first; bootstrap
|
|
261
|
+
// requires a bearer and must not run until then.
|
|
230
262
|
const active = this.state?.activeAccountId ?? null;
|
|
231
|
-
if (active && active !== this.host.getCurrentAccountId()) {
|
|
263
|
+
if (active && active !== this.host.getCurrentAccountId() && this.host.getAccessToken()) {
|
|
232
264
|
void this.bootstrap().catch((error) => {
|
|
233
265
|
loggerUtils_1.logger.warn('[SessionClient] post-push token fetch failed', { component: 'SessionClient' }, error);
|
|
234
266
|
});
|
|
@@ -31,6 +31,7 @@ exports.AccountDialogController = void 0;
|
|
|
31
31
|
exports.createAccountDialogController = createAccountDialogController;
|
|
32
32
|
const loggerUtils_1 = require("../utils/loggerUtils");
|
|
33
33
|
const authWebUrl_1 = require("../utils/authWebUrl");
|
|
34
|
+
const oauthPkce_1 = require("../utils/oauthPkce");
|
|
34
35
|
const accountProjection_1 = require("./accountProjection");
|
|
35
36
|
const DEFAULT_POLL_INTERVAL_MS = 3000;
|
|
36
37
|
const IDLE_SIGN_IN = {
|
|
@@ -72,6 +73,7 @@ class AccountDialogController {
|
|
|
72
73
|
this.commitSession = options.commitSession;
|
|
73
74
|
this.onSignedIn = options.onSignedIn;
|
|
74
75
|
this.idpApex = options.idpApex ?? authWebUrl_1.CENTRAL_IDP_APEX;
|
|
76
|
+
this.authRedirectUri = options.authRedirectUri ?? null;
|
|
75
77
|
this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
76
78
|
this.openUrl = options.openUrl;
|
|
77
79
|
this.snapshot = this.computeSnapshot();
|
|
@@ -425,18 +427,27 @@ class AccountDialogController {
|
|
|
425
427
|
* @param params.state - Optional opaque state echoed back on return.
|
|
426
428
|
* @returns The absolute auth.oxy.so sign-in URL.
|
|
427
429
|
*/
|
|
428
|
-
openPasswordAtOxyAuth(params = {}) {
|
|
430
|
+
async openPasswordAtOxyAuth(params = {}) {
|
|
429
431
|
const base = `https://auth.${this.idpApex}`;
|
|
430
432
|
const url = new URL('/login', base);
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
433
|
+
const rawRedirect = params.redirectUri ??
|
|
434
|
+
this.authRedirectUri ??
|
|
435
|
+
params.returnUrl ??
|
|
436
|
+
currentLocationOrigin();
|
|
437
|
+
const redirectUri = rawRedirect ? (0, oauthPkce_1.normalizeOAuthRedirectUri)(rawRedirect) : '';
|
|
438
|
+
if (redirectUri) {
|
|
439
|
+
url.searchParams.set('redirect_uri', redirectUri);
|
|
434
440
|
}
|
|
435
441
|
if (this.clientId) {
|
|
436
442
|
url.searchParams.set('client_id', this.clientId);
|
|
437
443
|
}
|
|
438
|
-
|
|
439
|
-
|
|
444
|
+
const state = params.state ?? (await (0, oauthPkce_1.generateOAuthState)());
|
|
445
|
+
url.searchParams.set('state', state);
|
|
446
|
+
const { codeChallenge, codeVerifier } = await (0, oauthPkce_1.generatePkcePair)();
|
|
447
|
+
url.searchParams.set('code_challenge', codeChallenge);
|
|
448
|
+
url.searchParams.set('code_challenge_method', 'S256');
|
|
449
|
+
if (!(0, oauthPkce_1.persistOAuthHandshake)(state, codeVerifier)) {
|
|
450
|
+
throw new Error('Could not persist OAuth handshake for password sign-in');
|
|
440
451
|
}
|
|
441
452
|
const href = url.toString();
|
|
442
453
|
this.openUrl?.(href);
|
|
@@ -516,6 +527,7 @@ class AccountDialogController {
|
|
|
516
527
|
expiresAt: claimed.expiresAt ?? '',
|
|
517
528
|
user: minimalUser,
|
|
518
529
|
accessToken: claimed.accessToken,
|
|
530
|
+
...(claimed.deviceSecret ? { deviceSecret: claimed.deviceSecret } : {}),
|
|
519
531
|
}, minimalUser);
|
|
520
532
|
}
|
|
521
533
|
catch (error) {
|
|
@@ -606,8 +618,8 @@ function createAccountDialogController(options) {
|
|
|
606
618
|
// ---------------------------------------------------------------------------
|
|
607
619
|
// Local helpers
|
|
608
620
|
// ---------------------------------------------------------------------------
|
|
609
|
-
/** Current document
|
|
610
|
-
function
|
|
621
|
+
/** Current document origin on web; empty string where `location` is absent (native/SSR). */
|
|
622
|
+
function currentLocationOrigin() {
|
|
611
623
|
const location = globalThis.location;
|
|
612
|
-
return typeof location?.
|
|
624
|
+
return typeof location?.origin === 'string' ? location.origin : '';
|
|
613
625
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* a reload restores the session locally without a redirect: `deviceId` +
|
|
7
7
|
* `deviceSecret` mint a fresh access token via `POST /session/device/token`.
|
|
8
8
|
* This module is the storage seam: a tiny `load / save / clear` interface plus
|
|
9
|
-
* platform factories, so the cold boot (`
|
|
9
|
+
* platform factories, so the cold boot (`sessionColdBoot`) and the unified re-mint
|
|
10
10
|
* handler (`refresh.ts`) never touch a platform storage API directly.
|
|
11
11
|
*
|
|
12
12
|
* Platform-agnostic — the native factory takes an INJECTED key/value store
|
|
@@ -48,15 +48,21 @@ function deserialize(raw) {
|
|
|
48
48
|
return null;
|
|
49
49
|
}
|
|
50
50
|
const candidate = parsed;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
candidate.
|
|
54
|
-
candidate.
|
|
51
|
+
const hasDeviceCredential = typeof candidate.deviceId === 'string' &&
|
|
52
|
+
candidate.deviceId.length > 0 &&
|
|
53
|
+
typeof candidate.deviceSecret === 'string' &&
|
|
54
|
+
candidate.deviceSecret.length > 0;
|
|
55
|
+
const hasSessionIdentity = typeof candidate.sessionId === 'string' &&
|
|
56
|
+
typeof candidate.userId === 'string' &&
|
|
57
|
+
candidate.sessionId.length > 0 &&
|
|
58
|
+
candidate.userId.length > 0;
|
|
59
|
+
// Device-only bootstrap (post join, pre-sign-in): durable credential without session.
|
|
60
|
+
if (!hasSessionIdentity && !hasDeviceCredential) {
|
|
55
61
|
return null;
|
|
56
62
|
}
|
|
57
63
|
const state = {
|
|
58
|
-
sessionId: candidate.sessionId,
|
|
59
|
-
userId: candidate.userId,
|
|
64
|
+
sessionId: hasSessionIdentity ? candidate.sessionId : '',
|
|
65
|
+
userId: hasSessionIdentity ? candidate.userId : '',
|
|
60
66
|
};
|
|
61
67
|
if (typeof candidate.deviceId === 'string' && candidate.deviceId.length > 0) {
|
|
62
68
|
state.deviceId = candidate.deviceId;
|
|
@@ -0,0 +1,55 @@
|
|
|
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}`;
|
|
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
|
+
}
|
|
@@ -15,15 +15,20 @@ exports.createSessionClientHost = createSessionClientHost;
|
|
|
15
15
|
*/
|
|
16
16
|
function createSessionClientHost(oxyServices) {
|
|
17
17
|
let currentAccountId = null;
|
|
18
|
+
let deviceCredential = null;
|
|
18
19
|
return {
|
|
19
20
|
makeRequest: (method, url, data, options) => oxyServices.makeRequest(method, url, data, options),
|
|
20
21
|
getBaseURL: () => oxyServices.getBaseURL(),
|
|
21
22
|
getAccessToken: () => oxyServices.getAccessToken(),
|
|
23
|
+
getDeviceCredential: () => deviceCredential,
|
|
22
24
|
onTokensChanged: (listener) => oxyServices.onTokensChanged(listener),
|
|
23
25
|
setTokens: (accessToken) => oxyServices.setTokens(accessToken),
|
|
24
26
|
getCurrentAccountId: () => currentAccountId,
|
|
25
27
|
setCurrentAccountId: (id) => {
|
|
26
28
|
currentAccountId = id;
|
|
27
29
|
},
|
|
30
|
+
setDeviceCredential: (credential) => {
|
|
31
|
+
deviceCredential = credential;
|
|
32
|
+
},
|
|
28
33
|
};
|
|
29
34
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* web bundle — the reason any run-once guard for a step must live in the
|
|
18
18
|
* calling consumer, never in a core module-level singleton).
|
|
19
19
|
* - Architecture-agnostic: it knows nothing about HOW a step resolves a
|
|
20
|
-
* session; `runSessionColdBoot` (`boot/
|
|
20
|
+
* session; `runSessionColdBoot` (`boot/sessionColdBoot.ts`) is the current
|
|
21
21
|
* device-first consumer.
|
|
22
22
|
*
|
|
23
23
|
* A step is skipped (without running) when its `enabled` predicate returns
|