@socialproof/mysocial-auth 0.2.0 → 0.4.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/CHANGELOG.md +12 -0
- package/README.md +19 -10
- package/dist/auth.d.mts.map +1 -1
- package/dist/auth.mjs +19 -15
- package/dist/auth.mjs.map +1 -1
- package/dist/exchange.mjs +1 -22
- package/dist/exchange.mjs.map +1 -1
- package/dist/pkce.mjs +1 -18
- package/dist/pkce.mjs.map +1 -1
- package/dist/popup.mjs +10 -19
- package/dist/popup.mjs.map +1 -1
- package/dist/types.d.mts +4 -2
- package/dist/types.d.mts.map +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ await auth.signOut();
|
|
|
68
68
|
|
|
69
69
|
### auth.signIn(options?)
|
|
70
70
|
|
|
71
|
-
- `provider?`: 'google' | 'apple' | 'facebook' | 'twitch'
|
|
71
|
+
- `provider?`: 'google' | 'apple' | 'facebook' | 'twitch' | 'none' (default 'none' = home screen)
|
|
72
72
|
- `mode?`: 'popup' | 'redirect' (default: 'popup')
|
|
73
73
|
- Returns: `Promise<Session>` (popup) or never (redirect; page navigates)
|
|
74
74
|
|
|
@@ -86,8 +86,8 @@ await auth.signOut();
|
|
|
86
86
|
|
|
87
87
|
### auth.handleRedirectCallback(url?)
|
|
88
88
|
|
|
89
|
-
- For redirect mode. Parses URL for code/state/nonce,
|
|
90
|
-
`window.location.href`.
|
|
89
|
+
- For redirect mode. Parses URL for code/state/nonce, builds session from payload (no exchange).
|
|
90
|
+
Omit `url` to use `window.location.href`.
|
|
91
91
|
|
|
92
92
|
### auth.onAuthStateChange(callback)
|
|
93
93
|
|
|
@@ -95,7 +95,10 @@ await auth.signOut();
|
|
|
95
95
|
|
|
96
96
|
## Hosted UI Contract (auth.mysocial.network)
|
|
97
97
|
|
|
98
|
-
The SDK passes `return_origin` in the login URL
|
|
98
|
+
The SDK passes `return_origin`, `code_challenge_method: S256`, and `provider` in the login URL
|
|
99
|
+
params. `provider` is never empty: `'google'`, `'apple'`, `'facebook'`, `'twitch'`, or `'none'`
|
|
100
|
+
(home screen). The auth frontend generates PKCE server-side; the package does not send
|
|
101
|
+
code_challenge. `return_origin` must match the dApp's origin
|
|
99
102
|
(`window.location.origin`) so the auth server can post the auth result to the correct target.
|
|
100
103
|
The auth server uses `return_origin` as the `postMessage` targetOrigin when sending the result.
|
|
101
104
|
|
|
@@ -105,7 +108,7 @@ The popup page must call:
|
|
|
105
108
|
window.opener.postMessage(payload, validatedTargetOrigin); // targetOrigin, NOT "*"
|
|
106
109
|
```
|
|
107
110
|
|
|
108
|
-
**Success payload:**
|
|
111
|
+
**Success payload (MYSOCIAL_AUTH_RESULT is the final result; no exchange needed):**
|
|
109
112
|
|
|
110
113
|
```json
|
|
111
114
|
{
|
|
@@ -114,10 +117,17 @@ window.opener.postMessage(payload, validatedTargetOrigin); // targetOrigin, NOT
|
|
|
114
117
|
"state": "...",
|
|
115
118
|
"nonce": "...",
|
|
116
119
|
"clientId": "...",
|
|
117
|
-
"requestId": "..."
|
|
120
|
+
"requestId": "...",
|
|
121
|
+
"salt": "...",
|
|
122
|
+
"user": {},
|
|
123
|
+
"access_token": "...",
|
|
124
|
+
"refresh_token": "...",
|
|
125
|
+
"expires_at": 0
|
|
118
126
|
}
|
|
119
127
|
```
|
|
120
128
|
|
|
129
|
+
`code` is the token (id_token or access_token). Optional: `salt`, `user`, `access_token`, `refresh_token`, `expires_at`.
|
|
130
|
+
|
|
121
131
|
**Error payload:**
|
|
122
132
|
|
|
123
133
|
```json
|
|
@@ -125,7 +135,6 @@ window.opener.postMessage(payload, validatedTargetOrigin); // targetOrigin, NOT
|
|
|
125
135
|
"type": "MYSOCIAL_AUTH_ERROR",
|
|
126
136
|
"error": "error_code_or_message",
|
|
127
137
|
"state": "...",
|
|
128
|
-
"nonce": "...",
|
|
129
138
|
"clientId": "...",
|
|
130
139
|
"requestId": "..."
|
|
131
140
|
}
|
|
@@ -134,13 +143,13 @@ window.opener.postMessage(payload, validatedTargetOrigin); // targetOrigin, NOT
|
|
|
134
143
|
`return_origin` must never be trusted from the query param. The backend must validate it against the
|
|
135
144
|
allowlist for `client_id`.
|
|
136
145
|
|
|
146
|
+
**Important:** The auth frontend and salt service handle the OAuth exchange internally. The package
|
|
147
|
+
does not call `/auth/exchange`. `MYSOCIAL_AUTH_RESULT` is the final result: `code` is the token.
|
|
148
|
+
|
|
137
149
|
## Backend Contract
|
|
138
150
|
|
|
139
151
|
- `POST ${apiBaseUrl}/auth/request` (optional): `{ client_id, redirect_uri, return_origin }` →
|
|
140
152
|
`{ request_id }`
|
|
141
|
-
- `POST ${apiBaseUrl}/auth/exchange`:
|
|
142
|
-
`{ code, code_verifier?, redirect_uri, state?, nonce?, request_id? }` →
|
|
143
|
-
`{ access_token, refresh_token?, expires_in, user }`
|
|
144
153
|
- `POST ${apiBaseUrl}/auth/refresh`: `{ refresh_token }` →
|
|
145
154
|
`{ access_token, refresh_token?, expires_in, user }`
|
|
146
155
|
- `POST ${apiBaseUrl}/auth/logout`
|
package/dist/auth.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.mts","names":[],"sources":["../src/auth.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"auth.d.mts","names":[],"sources":["../src/auth.ts"],"mappings":";;;UAiBiB,YAAA;EAChB,MAAA,CAAO,OAAA,GAAU,aAAA,GAAgB,OAAA,CAAQ,OAAA;EACzC,OAAA,IAAW,OAAA;EACX,UAAA,IAAc,OAAA,CAAQ,OAAA;EACtB,OAAA,IAAW,OAAA,CAAQ,OAAA;EACnB,sBAAA,CAAuB,GAAA,YAAe,OAAA,CAAQ,OAAA;EAC9C,iBAAA,CAAkB,QAAA,EAAU,uBAAA;AAAA"}
|
package/dist/auth.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { REDIRECT_STATE_PREFIX, SESSION_KEY, createStorage, redirectStorage } from "./storage.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { generateNonce, generateState } from "./pkce.mjs";
|
|
3
|
+
import { fetchRequestId, logout, refreshTokens } from "./exchange.mjs";
|
|
4
4
|
import { openAuthPopup } from "./popup.mjs";
|
|
5
5
|
import { mitt } from "@socialproof/utils";
|
|
6
6
|
|
|
@@ -67,8 +67,6 @@ function createAuth(config) {
|
|
|
67
67
|
}
|
|
68
68
|
const state = generateState();
|
|
69
69
|
const nonce = generateNonce();
|
|
70
|
-
const codeVerifier = await generateCodeVerifier();
|
|
71
|
-
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
72
70
|
const returnOrigin = getReturnOrigin(config.redirectUri);
|
|
73
71
|
let requestId;
|
|
74
72
|
if (config.useRequestId) requestId = await fetchRequestId(config.apiBaseUrl, {
|
|
@@ -79,7 +77,6 @@ function createAuth(config) {
|
|
|
79
77
|
const redirectState = {
|
|
80
78
|
state,
|
|
81
79
|
nonce,
|
|
82
|
-
codeVerifier,
|
|
83
80
|
requestId
|
|
84
81
|
};
|
|
85
82
|
const key = `${REDIRECT_STATE_PREFIX}${state}`;
|
|
@@ -91,8 +88,7 @@ function createAuth(config) {
|
|
|
91
88
|
nonce,
|
|
92
89
|
return_origin: returnOrigin,
|
|
93
90
|
mode: "redirect",
|
|
94
|
-
provider: provider ?? "",
|
|
95
|
-
code_challenge: codeChallenge,
|
|
91
|
+
provider: provider ?? "none",
|
|
96
92
|
code_challenge_method: "S256"
|
|
97
93
|
});
|
|
98
94
|
if (requestId) params.set("request_id", requestId);
|
|
@@ -126,6 +122,11 @@ function createAuth(config) {
|
|
|
126
122
|
const state = parsed.searchParams.get("state");
|
|
127
123
|
const nonce = parsed.searchParams.get("nonce");
|
|
128
124
|
const requestId = parsed.searchParams.get("request_id") ?? void 0;
|
|
125
|
+
const salt = parsed.searchParams.get("salt") ?? void 0;
|
|
126
|
+
const accessToken = parsed.searchParams.get("access_token") ?? void 0;
|
|
127
|
+
const refreshToken = parsed.searchParams.get("refresh_token") ?? void 0;
|
|
128
|
+
const expiresAtParam = parsed.searchParams.get("expires_at");
|
|
129
|
+
const userParam = parsed.searchParams.get("user");
|
|
129
130
|
if (!code || !state || !nonce) throw new Error("Missing code, state, or nonce in callback URL");
|
|
130
131
|
const key = `${REDIRECT_STATE_PREFIX}${state}`;
|
|
131
132
|
const raw = redirectStorage.get(key);
|
|
@@ -140,14 +141,17 @@ function createAuth(config) {
|
|
|
140
141
|
throw new Error("Request ID mismatch");
|
|
141
142
|
}
|
|
142
143
|
redirectStorage.remove(key);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
144
|
+
let user = {};
|
|
145
|
+
if (userParam) try {
|
|
146
|
+
user = JSON.parse(decodeURIComponent(userParam));
|
|
147
|
+
} catch {}
|
|
148
|
+
const session = {
|
|
149
|
+
access_token: accessToken ?? code,
|
|
150
|
+
refresh_token: refreshToken,
|
|
151
|
+
user,
|
|
152
|
+
expires_at: expiresAtParam ? Number(expiresAtParam) : Date.now() + 36e5,
|
|
153
|
+
...salt && { salt }
|
|
154
|
+
};
|
|
151
155
|
await saveSession(session);
|
|
152
156
|
emitter.emit("change", session);
|
|
153
157
|
return session;
|
package/dist/auth.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.mjs","names":[],"sources":["../src/auth.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\nimport { mitt, type Emitter } from '@socialproof/utils';\nimport type {\n\tMySocialAuthConfig,\n\tSignInOptions,\n\tSession,\n\tAuthStateChangeCallback,\n} from './types.js';\nimport { createStorage, redirectStorage, SESSION_KEY, REDIRECT_STATE_PREFIX } from './storage.js';\nimport { openAuthPopup } from './popup.js';\nimport { exchangeCode, refreshTokens, logout, fetchRequestId } from './exchange.js';\nimport {\n\tgenerateCodeVerifier,\n\tgenerateCodeChallenge,\n\tgenerateState,\n\tgenerateNonce,\n} from './pkce.js';\n\ntype AuthEvents = { change: Session | null };\n\nexport interface MySocialAuth {\n\tsignIn(options?: SignInOptions): Promise<Session>;\n\tsignOut(): Promise<void>;\n\tgetSession(): Promise<Session | null>;\n\trefresh(): Promise<Session | null>;\n\thandleRedirectCallback(url?: string): Promise<Session>;\n\tonAuthStateChange(callback: AuthStateChangeCallback): () => void;\n}\n\nfunction getReturnOrigin(redirectUri: string): string {\n\ttry {\n\t\tconst u = new URL(redirectUri);\n\t\treturn `${u.protocol}//${u.host}`;\n\t} catch {\n\t\treturn '';\n\t}\n}\n\nexport function createAuth(config: MySocialAuthConfig): MySocialAuth {\n\tconst storage = createStorage(config.storage);\n\tconst emitter: Emitter<AuthEvents> = mitt<AuthEvents>();\n\n\tlet cachedSession: Session | null = null;\n\n\tasync function loadSession(): Promise<Session | null> {\n\t\tconst raw = storage.get(SESSION_KEY);\n\t\tif (!raw) return null;\n\t\ttry {\n\t\t\tconst session = JSON.parse(raw) as Session;\n\t\t\tif (session.expires_at && session.expires_at > Date.now()) {\n\t\t\t\treturn session;\n\t\t\t}\n\t\t\tif (session.refresh_token) {\n\t\t\t\tconst refreshed = await refreshTokens(config.apiBaseUrl, session.refresh_token);\n\t\t\t\tawait saveSession(refreshed);\n\t\t\t\temitter.emit('change', refreshed);\n\t\t\t\treturn refreshed;\n\t\t\t}\n\t\t\tstorage.remove(SESSION_KEY);\n\t\t\tcachedSession = null;\n\t\t\temitter.emit('change', null);\n\t\t\treturn null;\n\t\t} catch {\n\t\t\tstorage.remove(SESSION_KEY);\n\t\t\tcachedSession = null;\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tasync function saveSession(session: Session): Promise<void> {\n\t\tcachedSession = session;\n\t\tstorage.set(SESSION_KEY, JSON.stringify(session));\n\t}\n\n\tasync function clearSession(): Promise<void> {\n\t\tcachedSession = null;\n\t\tstorage.remove(SESSION_KEY);\n\t}\n\n\treturn {\n\t\tasync signIn(options: SignInOptions = {}) {\n\t\t\tconst mode = options.mode ?? 'popup';\n\t\t\tconst provider = options.provider;\n\n\t\t\tif (mode === 'popup') {\n\t\t\t\tconst session = await openAuthPopup({\n\t\t\t\t\tapiBaseUrl: config.apiBaseUrl,\n\t\t\t\t\tauthOrigin: config.authOrigin,\n\t\t\t\t\tclientId: config.clientId,\n\t\t\t\t\tredirectUri: config.redirectUri,\n\t\t\t\t\tprovider,\n\t\t\t\t\ttimeout: config.popupTimeout ?? 120_000,\n\t\t\t\t\tuseRequestId: config.useRequestId ?? false,\n\t\t\t\t});\n\t\t\t\tawait saveSession(session);\n\t\t\t\temitter.emit('change', session);\n\t\t\t\treturn session;\n\t\t\t}\n\n\t\t\t// Redirect mode\n\t\t\tconst state = generateState();\n\t\t\tconst nonce = generateNonce();\n\t\t\tconst codeVerifier = await generateCodeVerifier();\n\t\t\tconst codeChallenge = await generateCodeChallenge(codeVerifier);\n\t\t\tconst returnOrigin = getReturnOrigin(config.redirectUri);\n\n\t\t\tlet requestId: string | undefined;\n\t\t\tif (config.useRequestId) {\n\t\t\t\trequestId = await fetchRequestId(config.apiBaseUrl, {\n\t\t\t\t\tclient_id: config.clientId,\n\t\t\t\t\tredirect_uri: config.redirectUri,\n\t\t\t\t\treturn_origin: returnOrigin,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst redirectState = {\n\t\t\t\tstate,\n\t\t\t\tnonce,\n\t\t\t\tcodeVerifier,\n\t\t\t\trequestId,\n\t\t\t};\n\t\t\tconst key = `${REDIRECT_STATE_PREFIX}${state}`;\n\t\t\tredirectStorage.set(key, JSON.stringify(redirectState));\n\n\t\t\tconst params = new URLSearchParams({\n\t\t\t\tclient_id: config.clientId,\n\t\t\t\tredirect_uri: config.redirectUri,\n\t\t\t\tstate,\n\t\t\t\tnonce,\n\t\t\t\treturn_origin: returnOrigin,\n\t\t\t\tmode: 'redirect',\n\t\t\t\tprovider: provider ?? '',\n\t\t\t\tcode_challenge: codeChallenge,\n\t\t\t\tcode_challenge_method: 'S256',\n\t\t\t});\n\t\t\tif (requestId) params.set('request_id', requestId);\n\n\t\t\tconst loginUrl = `${config.authOrigin.replace(/\\/$/, '')}/login?${params.toString()}`;\n\t\t\twindow.location.href = loginUrl;\n\n\t\t\treturn new Promise<Session>(() => {}); // Never resolves; page navigates away\n\t\t},\n\n\t\tasync signOut() {\n\t\t\ttry {\n\t\t\t\tawait logout(config.apiBaseUrl);\n\t\t\t} catch {\n\t\t\t\t// Best-effort; clear local state regardless\n\t\t\t}\n\t\t\tawait clearSession();\n\t\t\temitter.emit('change', null);\n\t\t},\n\n\t\tasync getSession() {\n\t\t\tif (cachedSession && cachedSession.expires_at > Date.now()) {\n\t\t\t\treturn cachedSession;\n\t\t\t}\n\t\t\treturn loadSession();\n\t\t},\n\n\t\tasync refresh() {\n\t\t\tconst session = await loadSession();\n\t\t\tif (!session?.refresh_token) return null;\n\t\t\tconst refreshed = await refreshTokens(config.apiBaseUrl, session.refresh_token);\n\t\t\tawait saveSession(refreshed);\n\t\t\temitter.emit('change', refreshed);\n\t\t\treturn refreshed;\n\t\t},\n\n\t\tasync handleRedirectCallback(url?: string) {\n\t\t\tconst targetUrl = url ?? (typeof window !== 'undefined' ? window.location.href : '');\n\t\t\tconst parsed = new URL(targetUrl);\n\t\t\tconst code = parsed.searchParams.get('code');\n\t\t\tconst state = parsed.searchParams.get('state');\n\t\t\tconst nonce = parsed.searchParams.get('nonce');\n\t\t\tconst requestId = parsed.searchParams.get('request_id') ?? undefined;\n\n\t\t\tif (!code || !state || !nonce) {\n\t\t\t\tthrow new Error('Missing code, state, or nonce in callback URL');\n\t\t\t}\n\n\t\t\tconst key = `${REDIRECT_STATE_PREFIX}${state}`;\n\t\t\tconst raw = redirectStorage.get(key);\n\t\t\tif (!raw) {\n\t\t\t\tthrow new Error('No matching redirect state found. Session may have expired.');\n\t\t\t}\n\n\t\t\tconst redirectState = JSON.parse(raw) as {\n\t\t\t\tstate: string;\n\t\t\t\tnonce: string;\n\t\t\t\tcodeVerifier: string;\n\t\t\t\trequestId?: string;\n\t\t\t};\n\n\t\t\tif (redirectState.state !== state || redirectState.nonce !== nonce) {\n\t\t\t\tredirectStorage.remove(key);\n\t\t\t\tthrow new Error('State or nonce mismatch');\n\t\t\t}\n\t\t\tif (requestId && redirectState.requestId !== requestId) {\n\t\t\t\tredirectStorage.remove(key);\n\t\t\t\tthrow new Error('Request ID mismatch');\n\t\t\t}\n\n\t\t\tredirectStorage.remove(key);\n\n\t\t\tconst session = await exchangeCode(config.apiBaseUrl, {\n\t\t\t\tcode,\n\t\t\t\tcode_verifier: redirectState.codeVerifier,\n\t\t\t\tredirect_uri: config.redirectUri,\n\t\t\t\tstate,\n\t\t\t\tnonce,\n\t\t\t\trequest_id: requestId ?? redirectState.requestId,\n\t\t\t});\n\n\t\t\tawait saveSession(session);\n\t\t\temitter.emit('change', session);\n\t\t\treturn session;\n\t\t},\n\n\t\tonAuthStateChange(callback: AuthStateChangeCallback) {\n\t\t\temitter.on('change', callback);\n\t\t\treturn () => emitter.off('change', callback);\n\t\t},\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"auth.mjs","names":[],"sources":["../src/auth.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\nimport { mitt, type Emitter } from '@socialproof/utils';\nimport type {\n\tMySocialAuthConfig,\n\tSignInOptions,\n\tSession,\n\tAuthStateChangeCallback,\n} from './types.js';\nimport { createStorage, redirectStorage, SESSION_KEY, REDIRECT_STATE_PREFIX } from './storage.js';\nimport { openAuthPopup } from './popup.js';\nimport { refreshTokens, logout, fetchRequestId } from './exchange.js';\nimport { generateState, generateNonce } from './pkce.js';\n\ntype AuthEvents = { change: Session | null };\n\nexport interface MySocialAuth {\n\tsignIn(options?: SignInOptions): Promise<Session>;\n\tsignOut(): Promise<void>;\n\tgetSession(): Promise<Session | null>;\n\trefresh(): Promise<Session | null>;\n\thandleRedirectCallback(url?: string): Promise<Session>;\n\tonAuthStateChange(callback: AuthStateChangeCallback): () => void;\n}\n\nfunction getReturnOrigin(redirectUri: string): string {\n\ttry {\n\t\tconst u = new URL(redirectUri);\n\t\treturn `${u.protocol}//${u.host}`;\n\t} catch {\n\t\treturn '';\n\t}\n}\n\nexport function createAuth(config: MySocialAuthConfig): MySocialAuth {\n\tconst storage = createStorage(config.storage);\n\tconst emitter: Emitter<AuthEvents> = mitt<AuthEvents>();\n\n\tlet cachedSession: Session | null = null;\n\n\tasync function loadSession(): Promise<Session | null> {\n\t\tconst raw = storage.get(SESSION_KEY);\n\t\tif (!raw) return null;\n\t\ttry {\n\t\t\tconst session = JSON.parse(raw) as Session;\n\t\t\tif (session.expires_at && session.expires_at > Date.now()) {\n\t\t\t\treturn session;\n\t\t\t}\n\t\t\tif (session.refresh_token) {\n\t\t\t\tconst refreshed = await refreshTokens(config.apiBaseUrl, session.refresh_token);\n\t\t\t\tawait saveSession(refreshed);\n\t\t\t\temitter.emit('change', refreshed);\n\t\t\t\treturn refreshed;\n\t\t\t}\n\t\t\tstorage.remove(SESSION_KEY);\n\t\t\tcachedSession = null;\n\t\t\temitter.emit('change', null);\n\t\t\treturn null;\n\t\t} catch {\n\t\t\tstorage.remove(SESSION_KEY);\n\t\t\tcachedSession = null;\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tasync function saveSession(session: Session): Promise<void> {\n\t\tcachedSession = session;\n\t\tstorage.set(SESSION_KEY, JSON.stringify(session));\n\t}\n\n\tasync function clearSession(): Promise<void> {\n\t\tcachedSession = null;\n\t\tstorage.remove(SESSION_KEY);\n\t}\n\n\treturn {\n\t\tasync signIn(options: SignInOptions = {}) {\n\t\t\tconst mode = options.mode ?? 'popup';\n\t\t\tconst provider = options.provider;\n\n\t\t\tif (mode === 'popup') {\n\t\t\t\tconst session = await openAuthPopup({\n\t\t\t\t\tapiBaseUrl: config.apiBaseUrl,\n\t\t\t\t\tauthOrigin: config.authOrigin,\n\t\t\t\t\tclientId: config.clientId,\n\t\t\t\t\tredirectUri: config.redirectUri,\n\t\t\t\t\tprovider,\n\t\t\t\t\ttimeout: config.popupTimeout ?? 120_000,\n\t\t\t\t\tuseRequestId: config.useRequestId ?? false,\n\t\t\t\t});\n\t\t\t\tawait saveSession(session);\n\t\t\t\temitter.emit('change', session);\n\t\t\t\treturn session;\n\t\t\t}\n\n\t\t\t// Redirect mode\n\t\t\tconst state = generateState();\n\t\t\tconst nonce = generateNonce();\n\t\t\tconst returnOrigin = getReturnOrigin(config.redirectUri);\n\n\t\t\tlet requestId: string | undefined;\n\t\t\tif (config.useRequestId) {\n\t\t\t\trequestId = await fetchRequestId(config.apiBaseUrl, {\n\t\t\t\t\tclient_id: config.clientId,\n\t\t\t\t\tredirect_uri: config.redirectUri,\n\t\t\t\t\treturn_origin: returnOrigin,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst redirectState = {\n\t\t\t\tstate,\n\t\t\t\tnonce,\n\t\t\t\trequestId,\n\t\t\t};\n\t\t\tconst key = `${REDIRECT_STATE_PREFIX}${state}`;\n\t\t\tredirectStorage.set(key, JSON.stringify(redirectState));\n\n\t\t\tconst params = new URLSearchParams({\n\t\t\t\tclient_id: config.clientId,\n\t\t\t\tredirect_uri: config.redirectUri,\n\t\t\t\tstate,\n\t\t\t\tnonce,\n\t\t\t\treturn_origin: returnOrigin,\n\t\t\t\tmode: 'redirect',\n\t\t\t\tprovider: provider ?? 'none',\n\t\t\t\tcode_challenge_method: 'S256',\n\t\t\t});\n\t\t\tif (requestId) params.set('request_id', requestId);\n\n\t\t\tconst loginUrl = `${config.authOrigin.replace(/\\/$/, '')}/login?${params.toString()}`;\n\t\t\twindow.location.href = loginUrl;\n\n\t\t\treturn new Promise<Session>(() => {}); // Never resolves; page navigates away\n\t\t},\n\n\t\tasync signOut() {\n\t\t\ttry {\n\t\t\t\tawait logout(config.apiBaseUrl);\n\t\t\t} catch {\n\t\t\t\t// Best-effort; clear local state regardless\n\t\t\t}\n\t\t\tawait clearSession();\n\t\t\temitter.emit('change', null);\n\t\t},\n\n\t\tasync getSession() {\n\t\t\tif (cachedSession && cachedSession.expires_at > Date.now()) {\n\t\t\t\treturn cachedSession;\n\t\t\t}\n\t\t\treturn loadSession();\n\t\t},\n\n\t\tasync refresh() {\n\t\t\tconst session = await loadSession();\n\t\t\tif (!session?.refresh_token) return null;\n\t\t\tconst refreshed = await refreshTokens(config.apiBaseUrl, session.refresh_token);\n\t\t\tawait saveSession(refreshed);\n\t\t\temitter.emit('change', refreshed);\n\t\t\treturn refreshed;\n\t\t},\n\n\t\tasync handleRedirectCallback(url?: string) {\n\t\t\tconst targetUrl = url ?? (typeof window !== 'undefined' ? window.location.href : '');\n\t\t\tconst parsed = new URL(targetUrl);\n\t\t\tconst code = parsed.searchParams.get('code');\n\t\t\tconst state = parsed.searchParams.get('state');\n\t\t\tconst nonce = parsed.searchParams.get('nonce');\n\t\t\tconst requestId = parsed.searchParams.get('request_id') ?? undefined;\n\t\t\tconst salt = parsed.searchParams.get('salt') ?? undefined;\n\t\t\tconst accessToken = parsed.searchParams.get('access_token') ?? undefined;\n\t\t\tconst refreshToken = parsed.searchParams.get('refresh_token') ?? undefined;\n\t\t\tconst expiresAtParam = parsed.searchParams.get('expires_at');\n\t\t\tconst userParam = parsed.searchParams.get('user');\n\n\t\t\tif (!code || !state || !nonce) {\n\t\t\t\tthrow new Error('Missing code, state, or nonce in callback URL');\n\t\t\t}\n\n\t\t\tconst key = `${REDIRECT_STATE_PREFIX}${state}`;\n\t\t\tconst raw = redirectStorage.get(key);\n\t\t\tif (!raw) {\n\t\t\t\tthrow new Error('No matching redirect state found. Session may have expired.');\n\t\t\t}\n\n\t\t\tconst redirectState = JSON.parse(raw) as {\n\t\t\t\tstate: string;\n\t\t\t\tnonce: string;\n\t\t\t\trequestId?: string;\n\t\t\t};\n\n\t\t\tif (redirectState.state !== state || redirectState.nonce !== nonce) {\n\t\t\t\tredirectStorage.remove(key);\n\t\t\t\tthrow new Error('State or nonce mismatch');\n\t\t\t}\n\t\t\tif (requestId && redirectState.requestId !== requestId) {\n\t\t\t\tredirectStorage.remove(key);\n\t\t\t\tthrow new Error('Request ID mismatch');\n\t\t\t}\n\n\t\t\tredirectStorage.remove(key);\n\n\t\t\tlet user: Session['user'] = {};\n\t\t\tif (userParam) {\n\t\t\t\ttry {\n\t\t\t\t\tuser = JSON.parse(decodeURIComponent(userParam)) as Session['user'];\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore invalid user param\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst session: Session = {\n\t\t\t\taccess_token: accessToken ?? code,\n\t\t\t\trefresh_token: refreshToken,\n\t\t\t\tuser,\n\t\t\t\texpires_at: expiresAtParam ? Number(expiresAtParam) : Date.now() + 3600_000,\n\t\t\t\t...(salt && { salt }),\n\t\t\t};\n\n\t\t\tawait saveSession(session);\n\t\t\temitter.emit('change', session);\n\t\t\treturn session;\n\t\t},\n\n\t\tonAuthStateChange(callback: AuthStateChangeCallback) {\n\t\t\temitter.on('change', callback);\n\t\t\treturn () => emitter.off('change', callback);\n\t\t},\n\t};\n}\n"],"mappings":"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"}
|
package/dist/exchange.mjs
CHANGED
|
@@ -13,27 +13,6 @@ async function fetchRequestId(apiBaseUrl, body) {
|
|
|
13
13
|
}
|
|
14
14
|
return (await res.json()).request_id;
|
|
15
15
|
}
|
|
16
|
-
/** Exchange auth code for tokens */
|
|
17
|
-
async function exchangeCode(apiBaseUrl, body) {
|
|
18
|
-
const url = `${apiBaseUrl.replace(/\/$/, "")}/auth/exchange`;
|
|
19
|
-
const res = await fetch(url, {
|
|
20
|
-
method: "POST",
|
|
21
|
-
headers: { "Content-Type": "application/json" },
|
|
22
|
-
body: JSON.stringify(body)
|
|
23
|
-
});
|
|
24
|
-
if (!res.ok) {
|
|
25
|
-
const text = await res.text();
|
|
26
|
-
throw new Error(`Token exchange failed: ${res.status} ${text}`);
|
|
27
|
-
}
|
|
28
|
-
const data = await res.json();
|
|
29
|
-
const expires_at = Date.now() + data.expires_in * 1e3;
|
|
30
|
-
return {
|
|
31
|
-
access_token: data.access_token,
|
|
32
|
-
refresh_token: data.refresh_token,
|
|
33
|
-
expires_at,
|
|
34
|
-
user: data.user
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
16
|
/** Refresh access token using refresh_token */
|
|
38
17
|
async function refreshTokens(apiBaseUrl, refreshToken) {
|
|
39
18
|
const url = `${apiBaseUrl.replace(/\/$/, "")}/auth/refresh`;
|
|
@@ -69,5 +48,5 @@ async function logout(apiBaseUrl) {
|
|
|
69
48
|
}
|
|
70
49
|
|
|
71
50
|
//#endregion
|
|
72
|
-
export {
|
|
51
|
+
export { fetchRequestId, logout, refreshTokens };
|
|
73
52
|
//# sourceMappingURL=exchange.mjs.map
|
package/dist/exchange.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exchange.mjs","names":[],"sources":["../src/exchange.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type {\n\tAuthRequestRequest,\n\tAuthRequestResponse,\n\tExchangeRequest,\n\tExchangeResponse,\n\tSession,\n} from './types.js';\n\n/** Fetch request_id from backend (optional flow). Backend validates return_origin against allowlist. */\nexport async function fetchRequestId(\n\tapiBaseUrl: string,\n\tbody: AuthRequestRequest,\n): Promise<string> {\n\tconst url = `${apiBaseUrl.replace(/\\/$/, '')}/auth/request`;\n\tconst res = await fetch(url, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t\tbody: JSON.stringify(body),\n\t});\n\tif (!res.ok) {\n\t\tconst text = await res.text();\n\t\tthrow new Error(`Auth request failed: ${res.status} ${text}`);\n\t}\n\tconst data = (await res.json()) as AuthRequestResponse;\n\treturn data.request_id;\n}\n\n/** Exchange auth code for tokens */\nexport async function exchangeCode(apiBaseUrl: string, body: ExchangeRequest): Promise<Session> {\n\tconst url = `${apiBaseUrl.replace(/\\/$/, '')}/auth/exchange`;\n\tconst res = await fetch(url, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t\tbody: JSON.stringify(body),\n\t});\n\tif (!res.ok) {\n\t\tconst text = await res.text();\n\t\tthrow new Error(`Token exchange failed: ${res.status} ${text}`);\n\t}\n\tconst data = (await res.json()) as ExchangeResponse;\n\tconst expires_at = Date.now() + data.expires_in * 1000;\n\treturn {\n\t\taccess_token: data.access_token,\n\t\trefresh_token: data.refresh_token,\n\t\texpires_at,\n\t\tuser: data.user,\n\t};\n}\n\n/** Refresh access token using refresh_token */\nexport async function refreshTokens(apiBaseUrl: string, refreshToken: string): Promise<Session> {\n\tconst url = `${apiBaseUrl.replace(/\\/$/, '')}/auth/refresh`;\n\tconst res = await fetch(url, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t\tbody: JSON.stringify({ refresh_token: refreshToken }),\n\t});\n\tif (!res.ok) {\n\t\tconst text = await res.text();\n\t\tthrow new Error(`Token refresh failed: ${res.status} ${text}`);\n\t}\n\tconst data = (await res.json()) as ExchangeResponse;\n\tconst expires_at = Date.now() + data.expires_in * 1000;\n\treturn {\n\t\taccess_token: data.access_token,\n\t\trefresh_token: data.refresh_token ?? refreshToken,\n\t\texpires_at,\n\t\tuser: data.user,\n\t};\n}\n\n/** Logout / invalidate session on backend */\nexport async function logout(apiBaseUrl: string): Promise<void> {\n\tconst url = `${apiBaseUrl.replace(/\\/$/, '')}/auth/logout`;\n\tconst res = await fetch(url, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t});\n\tif (!res.ok) {\n\t\tconst text = await res.text();\n\t\tthrow new Error(`Logout failed: ${res.status} ${text}`);\n\t}\n}\n"],"mappings":";;AAYA,eAAsB,eACrB,YACA,MACkB;CAClB,MAAM,MAAM,GAAG,WAAW,QAAQ,OAAO,GAAG,CAAC;CAC7C,MAAM,MAAM,MAAM,MAAM,KAAK;EAC5B,QAAQ;EACR,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,MAAM,KAAK,UAAU,KAAK;EAC1B,CAAC;AACF,KAAI,CAAC,IAAI,IAAI;EACZ,MAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,QAAM,IAAI,MAAM,wBAAwB,IAAI,OAAO,GAAG,OAAO;;AAG9D,SADc,MAAM,IAAI,MAAM,EAClB;;;
|
|
1
|
+
{"version":3,"file":"exchange.mjs","names":[],"sources":["../src/exchange.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type {\n\tAuthRequestRequest,\n\tAuthRequestResponse,\n\tExchangeRequest,\n\tExchangeResponse,\n\tSession,\n} from './types.js';\n\n/** Fetch request_id from backend (optional flow). Backend validates return_origin against allowlist. */\nexport async function fetchRequestId(\n\tapiBaseUrl: string,\n\tbody: AuthRequestRequest,\n): Promise<string> {\n\tconst url = `${apiBaseUrl.replace(/\\/$/, '')}/auth/request`;\n\tconst res = await fetch(url, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t\tbody: JSON.stringify(body),\n\t});\n\tif (!res.ok) {\n\t\tconst text = await res.text();\n\t\tthrow new Error(`Auth request failed: ${res.status} ${text}`);\n\t}\n\tconst data = (await res.json()) as AuthRequestResponse;\n\treturn data.request_id;\n}\n\n/** Exchange auth code for tokens */\nexport async function exchangeCode(apiBaseUrl: string, body: ExchangeRequest): Promise<Session> {\n\tconst url = `${apiBaseUrl.replace(/\\/$/, '')}/auth/exchange`;\n\tconst res = await fetch(url, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t\tbody: JSON.stringify(body),\n\t});\n\tif (!res.ok) {\n\t\tconst text = await res.text();\n\t\tthrow new Error(`Token exchange failed: ${res.status} ${text}`);\n\t}\n\tconst data = (await res.json()) as ExchangeResponse;\n\tconst expires_at = Date.now() + data.expires_in * 1000;\n\treturn {\n\t\taccess_token: data.access_token,\n\t\trefresh_token: data.refresh_token,\n\t\texpires_at,\n\t\tuser: data.user,\n\t};\n}\n\n/** Refresh access token using refresh_token */\nexport async function refreshTokens(apiBaseUrl: string, refreshToken: string): Promise<Session> {\n\tconst url = `${apiBaseUrl.replace(/\\/$/, '')}/auth/refresh`;\n\tconst res = await fetch(url, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t\tbody: JSON.stringify({ refresh_token: refreshToken }),\n\t});\n\tif (!res.ok) {\n\t\tconst text = await res.text();\n\t\tthrow new Error(`Token refresh failed: ${res.status} ${text}`);\n\t}\n\tconst data = (await res.json()) as ExchangeResponse;\n\tconst expires_at = Date.now() + data.expires_in * 1000;\n\treturn {\n\t\taccess_token: data.access_token,\n\t\trefresh_token: data.refresh_token ?? refreshToken,\n\t\texpires_at,\n\t\tuser: data.user,\n\t};\n}\n\n/** Logout / invalidate session on backend */\nexport async function logout(apiBaseUrl: string): Promise<void> {\n\tconst url = `${apiBaseUrl.replace(/\\/$/, '')}/auth/logout`;\n\tconst res = await fetch(url, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t});\n\tif (!res.ok) {\n\t\tconst text = await res.text();\n\t\tthrow new Error(`Logout failed: ${res.status} ${text}`);\n\t}\n}\n"],"mappings":";;AAYA,eAAsB,eACrB,YACA,MACkB;CAClB,MAAM,MAAM,GAAG,WAAW,QAAQ,OAAO,GAAG,CAAC;CAC7C,MAAM,MAAM,MAAM,MAAM,KAAK;EAC5B,QAAQ;EACR,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,MAAM,KAAK,UAAU,KAAK;EAC1B,CAAC;AACF,KAAI,CAAC,IAAI,IAAI;EACZ,MAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,QAAM,IAAI,MAAM,wBAAwB,IAAI,OAAO,GAAG,OAAO;;AAG9D,SADc,MAAM,IAAI,MAAM,EAClB;;;AA0Bb,eAAsB,cAAc,YAAoB,cAAwC;CAC/F,MAAM,MAAM,GAAG,WAAW,QAAQ,OAAO,GAAG,CAAC;CAC7C,MAAM,MAAM,MAAM,MAAM,KAAK;EAC5B,QAAQ;EACR,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,MAAM,KAAK,UAAU,EAAE,eAAe,cAAc,CAAC;EACrD,CAAC;AACF,KAAI,CAAC,IAAI,IAAI;EACZ,MAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,QAAM,IAAI,MAAM,yBAAyB,IAAI,OAAO,GAAG,OAAO;;CAE/D,MAAM,OAAQ,MAAM,IAAI,MAAM;CAC9B,MAAM,aAAa,KAAK,KAAK,GAAG,KAAK,aAAa;AAClD,QAAO;EACN,cAAc,KAAK;EACnB,eAAe,KAAK,iBAAiB;EACrC;EACA,MAAM,KAAK;EACX;;;AAIF,eAAsB,OAAO,YAAmC;CAC/D,MAAM,MAAM,GAAG,WAAW,QAAQ,OAAO,GAAG,CAAC;CAC7C,MAAM,MAAM,MAAM,MAAM,KAAK;EAC5B,QAAQ;EACR,SAAS,EAAE,gBAAgB,oBAAoB;EAC/C,CAAC;AACF,KAAI,CAAC,IAAI,IAAI;EACZ,MAAM,OAAO,MAAM,IAAI,MAAM;AAC7B,QAAM,IAAI,MAAM,kBAAkB,IAAI,OAAO,GAAG,OAAO"}
|
package/dist/pkce.mjs
CHANGED
|
@@ -1,22 +1,5 @@
|
|
|
1
1
|
//#region src/pkce.ts
|
|
2
2
|
/**
|
|
3
|
-
* Generate a cryptographically random code verifier (32 bytes = 256 bits, base64url).
|
|
4
|
-
* Used for PKCE in OAuth flows.
|
|
5
|
-
*/
|
|
6
|
-
async function generateCodeVerifier() {
|
|
7
|
-
const array = new Uint8Array(32);
|
|
8
|
-
crypto.getRandomValues(array);
|
|
9
|
-
return base64UrlEncode(array);
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Generate code challenge from verifier using SHA-256 (S256 method).
|
|
13
|
-
*/
|
|
14
|
-
async function generateCodeChallenge(verifier) {
|
|
15
|
-
const data = new TextEncoder().encode(verifier);
|
|
16
|
-
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
17
|
-
return base64UrlEncode(new Uint8Array(hash));
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
3
|
* Generate a random state (128 bits+ entropy) for CSRF protection.
|
|
21
4
|
*/
|
|
22
5
|
function generateState() {
|
|
@@ -37,5 +20,5 @@ function base64UrlEncode(array) {
|
|
|
37
20
|
}
|
|
38
21
|
|
|
39
22
|
//#endregion
|
|
40
|
-
export {
|
|
23
|
+
export { generateNonce, generateState };
|
|
41
24
|
//# sourceMappingURL=pkce.mjs.map
|
package/dist/pkce.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pkce.mjs","names":[],"sources":["../src/pkce.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Generate a cryptographically random code verifier (32 bytes = 256 bits, base64url).\n * Used for PKCE in OAuth flows.\n */\nexport async function generateCodeVerifier(): Promise<string> {\n\tconst array = new Uint8Array(32);\n\tcrypto.getRandomValues(array);\n\treturn base64UrlEncode(array);\n}\n\n/**\n * Generate code challenge from verifier using SHA-256 (S256 method).\n */\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n\tconst encoder = new TextEncoder();\n\tconst data = encoder.encode(verifier);\n\tconst hash = await crypto.subtle.digest('SHA-256', data);\n\treturn base64UrlEncode(new Uint8Array(hash));\n}\n\n/**\n * Generate a random state (128 bits+ entropy) for CSRF protection.\n */\nexport function generateState(): string {\n\tconst array = new Uint8Array(16);\n\tcrypto.getRandomValues(array);\n\treturn base64UrlEncode(array);\n}\n\n/**\n * Generate a random nonce (128 bits+ entropy) for replay protection.\n */\nexport function generateNonce(): string {\n\tconst array = new Uint8Array(16);\n\tcrypto.getRandomValues(array);\n\treturn base64UrlEncode(array);\n}\n\nfunction base64UrlEncode(array: Uint8Array): string {\n\tconst base64 = btoa(String.fromCharCode(...array));\n\treturn base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"pkce.mjs","names":[],"sources":["../src/pkce.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Generate a cryptographically random code verifier (32 bytes = 256 bits, base64url).\n * Used for PKCE in OAuth flows.\n */\nexport async function generateCodeVerifier(): Promise<string> {\n\tconst array = new Uint8Array(32);\n\tcrypto.getRandomValues(array);\n\treturn base64UrlEncode(array);\n}\n\n/**\n * Generate code challenge from verifier using SHA-256 (S256 method).\n */\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n\tconst encoder = new TextEncoder();\n\tconst data = encoder.encode(verifier);\n\tconst hash = await crypto.subtle.digest('SHA-256', data);\n\treturn base64UrlEncode(new Uint8Array(hash));\n}\n\n/**\n * Generate a random state (128 bits+ entropy) for CSRF protection.\n */\nexport function generateState(): string {\n\tconst array = new Uint8Array(16);\n\tcrypto.getRandomValues(array);\n\treturn base64UrlEncode(array);\n}\n\n/**\n * Generate a random nonce (128 bits+ entropy) for replay protection.\n */\nexport function generateNonce(): string {\n\tconst array = new Uint8Array(16);\n\tcrypto.getRandomValues(array);\n\treturn base64UrlEncode(array);\n}\n\nfunction base64UrlEncode(array: Uint8Array): string {\n\tconst base64 = btoa(String.fromCharCode(...array));\n\treturn base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n"],"mappings":";;;;AA0BA,SAAgB,gBAAwB;CACvC,MAAM,QAAQ,IAAI,WAAW,GAAG;AAChC,QAAO,gBAAgB,MAAM;AAC7B,QAAO,gBAAgB,MAAM;;;;;AAM9B,SAAgB,gBAAwB;CACvC,MAAM,QAAQ,IAAI,WAAW,GAAG;AAChC,QAAO,gBAAgB,MAAM;AAC7B,QAAO,gBAAgB,MAAM;;AAG9B,SAAS,gBAAgB,OAA2B;AAEnD,QADe,KAAK,OAAO,aAAa,GAAG,MAAM,CAAC,CACpC,QAAQ,OAAO,IAAI,CAAC,QAAQ,OAAO,IAAI,CAAC,QAAQ,OAAO,GAAG"}
|
package/dist/popup.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AuthTimeoutError, InvalidStateError, PopupBlockedError, PopupClosedError } from "./errors.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { generateNonce, generateState } from "./pkce.mjs";
|
|
3
3
|
import { getPopupFeatures } from "./popup-utils.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { fetchRequestId } from "./exchange.mjs";
|
|
5
5
|
|
|
6
6
|
//#region src/popup.ts
|
|
7
7
|
/** Get return_origin from redirectUri (e.g. https://dripdrop.social/auth/callback -> https://dripdrop.social) */
|
|
@@ -28,13 +28,6 @@ async function openAuthPopup(options) {
|
|
|
28
28
|
redirect_uri: redirectUri,
|
|
29
29
|
return_origin: returnOrigin
|
|
30
30
|
});
|
|
31
|
-
const { codeVerifier, codeChallenge } = await (async () => {
|
|
32
|
-
const verifier = await generateCodeVerifier();
|
|
33
|
-
return {
|
|
34
|
-
codeVerifier: verifier,
|
|
35
|
-
codeChallenge: await generateCodeChallenge(verifier)
|
|
36
|
-
};
|
|
37
|
-
})();
|
|
38
31
|
const features = getPopupFeatures(420, 720);
|
|
39
32
|
const popup = window.open("about:blank", "_blank", features);
|
|
40
33
|
if (!popup || popup.closed) throw new PopupBlockedError();
|
|
@@ -45,8 +38,7 @@ async function openAuthPopup(options) {
|
|
|
45
38
|
nonce,
|
|
46
39
|
return_origin: returnOrigin,
|
|
47
40
|
mode: "popup",
|
|
48
|
-
provider: provider ?? "",
|
|
49
|
-
code_challenge: codeChallenge,
|
|
41
|
+
provider: provider ?? "none",
|
|
50
42
|
code_challenge_method: "S256"
|
|
51
43
|
});
|
|
52
44
|
if (requestId) params.set("request_id", requestId);
|
|
@@ -86,14 +78,13 @@ async function openAuthPopup(options) {
|
|
|
86
78
|
try {
|
|
87
79
|
popup.close();
|
|
88
80
|
} catch {}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}).then(resolve).catch(reject);
|
|
81
|
+
resolve({
|
|
82
|
+
access_token: msg.access_token ?? msg.code,
|
|
83
|
+
refresh_token: msg.refresh_token,
|
|
84
|
+
user: msg.user ?? {},
|
|
85
|
+
expires_at: msg.expires_at ?? Date.now() + 36e5,
|
|
86
|
+
...msg.salt && { salt: msg.salt }
|
|
87
|
+
});
|
|
97
88
|
} else if (data.type === "MYSOCIAL_AUTH_ERROR") {
|
|
98
89
|
const msg = data;
|
|
99
90
|
cleanup();
|
package/dist/popup.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"popup.mjs","names":[],"sources":["../src/popup.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { AuthProvider, AuthResultMessage, AuthErrorMessage } from './types.js';\nimport {\n\tAuthTimeoutError,\n\tInvalidStateError,\n\tPopupBlockedError,\n\tPopupClosedError,\n} from './errors.js';\nimport {
|
|
1
|
+
{"version":3,"file":"popup.mjs","names":[],"sources":["../src/popup.ts"],"sourcesContent":["// Copyright (c) The Social Proof Foundation, LLC.\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { AuthProvider, AuthResultMessage, AuthErrorMessage } from './types.js';\nimport {\n\tAuthTimeoutError,\n\tInvalidStateError,\n\tPopupBlockedError,\n\tPopupClosedError,\n} from './errors.js';\nimport { generateNonce, generateState } from './pkce.js';\nimport { getPopupFeatures } from './popup-utils.js';\nimport { fetchRequestId } from './exchange.js';\nimport type { Session } from './types.js';\n\n/** Get return_origin from redirectUri (e.g. https://dripdrop.social/auth/callback -> https://dripdrop.social) */\nfunction getReturnOrigin(redirectUri: string): string {\n\ttry {\n\t\tconst u = new URL(redirectUri);\n\t\treturn `${u.protocol}//${u.host}`;\n\t} catch {\n\t\treturn '';\n\t}\n}\n\nexport interface OpenPopupOptions {\n\tapiBaseUrl: string;\n\tauthOrigin: string;\n\tclientId: string;\n\tredirectUri: string;\n\tprovider?: AuthProvider;\n\ttimeout?: number;\n\tuseRequestId?: boolean;\n}\n\n/**\n * Open auth popup and wait for postMessage. Safari: open with about:blank first, then set location.\n * Returns session on success. Rejects on error, timeout, or user close.\n */\nexport async function openAuthPopup(options: OpenPopupOptions): Promise<Session> {\n\tconst {\n\t\tapiBaseUrl,\n\t\tauthOrigin,\n\t\tclientId,\n\t\tredirectUri,\n\t\tprovider,\n\t\ttimeout = 120_000,\n\t\tuseRequestId = false,\n\t} = options;\n\n\tconst state = generateState();\n\tconst nonce = generateNonce();\n\tconst returnOrigin =\n\t\ttypeof window !== 'undefined' ? window.location.origin : getReturnOrigin(redirectUri);\n\n\tlet requestId: string | undefined;\n\tif (useRequestId) {\n\t\trequestId = await fetchRequestId(apiBaseUrl, {\n\t\t\tclient_id: clientId,\n\t\t\tredirect_uri: redirectUri,\n\t\t\treturn_origin: returnOrigin,\n\t\t});\n\t}\n\n\t// Open popup immediately (Safari requires user gesture). Use about:blank, then set location.\n\tconst features = getPopupFeatures(420, 720);\n\tconst popup = window.open('about:blank', '_blank', features);\n\n\tif (!popup || popup.closed) {\n\t\tthrow new PopupBlockedError();\n\t}\n\n\tconst params = new URLSearchParams({\n\t\tclient_id: clientId,\n\t\tredirect_uri: redirectUri,\n\t\tstate,\n\t\tnonce,\n\t\treturn_origin: returnOrigin,\n\t\tmode: 'popup',\n\t\tprovider: provider ?? 'none',\n\t\tcode_challenge_method: 'S256',\n\t});\n\tif (requestId) params.set('request_id', requestId);\n\n\tconst loginUrl = `${authOrigin.replace(/\\/$/, '')}/login?${params.toString()}`;\n\n\t// Set location after popup is open (Safari-safe)\n\tpopup.location.href = loginUrl;\n\n\treturn new Promise<Session>((resolve, reject) => {\n\t\tlet timeoutId: ReturnType<typeof setTimeout> | null = null;\n\t\tlet closedCheckId: ReturnType<typeof setInterval> | null = null;\n\n\t\tconst cleanup = () => {\n\t\t\tif (timeoutId) clearTimeout(timeoutId);\n\t\t\tif (closedCheckId) clearInterval(closedCheckId);\n\t\t\twindow.removeEventListener('message', handler);\n\t\t};\n\n\t\tconst handler = (event: MessageEvent) => {\n\t\t\tif (event.origin !== authOrigin) return;\n\t\t\tif (event.source !== popup) return;\n\n\t\t\tconst data = event.data;\n\t\t\tif (!data || typeof data !== 'object' || !data.type) return;\n\n\t\t\tif (data.type === 'MYSOCIAL_AUTH_RESULT') {\n\t\t\t\tconst msg = data as AuthResultMessage;\n\t\t\t\tif (msg.state !== state || msg.nonce !== nonce) {\n\t\t\t\t\tcleanup();\n\t\t\t\t\treject(new InvalidStateError());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (msg.clientId !== clientId) {\n\t\t\t\t\tcleanup();\n\t\t\t\t\treject(new InvalidStateError());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (requestId && msg.requestId !== requestId) {\n\t\t\t\t\tcleanup();\n\t\t\t\t\treject(new InvalidStateError());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tcleanup();\n\t\t\t\ttry {\n\t\t\t\t\tpopup.close();\n\t\t\t\t} catch {\n\t\t\t\t\t// Popup may already be closed by auth server\n\t\t\t\t}\n\n\t\t\t\tconst session: Session = {\n\t\t\t\t\taccess_token: msg.access_token ?? msg.code,\n\t\t\t\t\trefresh_token: msg.refresh_token,\n\t\t\t\t\tuser: msg.user ?? {},\n\t\t\t\t\texpires_at: msg.expires_at ?? Date.now() + 3600_000,\n\t\t\t\t\t...(msg.salt && { salt: msg.salt }),\n\t\t\t\t};\n\t\t\t\tresolve(session);\n\t\t\t} else if (data.type === 'MYSOCIAL_AUTH_ERROR') {\n\t\t\t\tconst msg = data as AuthErrorMessage;\n\t\t\t\tcleanup();\n\t\t\t\ttry {\n\t\t\t\t\tpopup.close();\n\t\t\t\t} catch {\n\t\t\t\t\t// Popup may already be closed\n\t\t\t\t}\n\t\t\t\treject(new Error(msg.error ?? 'Authentication failed'));\n\t\t\t}\n\t\t};\n\n\t\twindow.addEventListener('message', handler);\n\n\t\ttimeoutId = setTimeout(() => {\n\t\t\tcleanup();\n\t\t\ttry {\n\t\t\t\tpopup.close();\n\t\t\t} catch {\n\t\t\t\t// Popup may already be closed\n\t\t\t}\n\t\t\treject(new AuthTimeoutError());\n\t\t}, timeout);\n\n\t\tclosedCheckId = setInterval(() => {\n\t\t\tif (popup.closed) {\n\t\t\t\tcleanup();\n\t\t\t\treject(new PopupClosedError());\n\t\t\t}\n\t\t}, 200);\n\t});\n}\n"],"mappings":";;;;;;;AAgBA,SAAS,gBAAgB,aAA6B;AACrD,KAAI;EACH,MAAM,IAAI,IAAI,IAAI,YAAY;AAC9B,SAAO,GAAG,EAAE,SAAS,IAAI,EAAE;SACpB;AACP,SAAO;;;;;;;AAkBT,eAAsB,cAAc,SAA6C;CAChF,MAAM,EACL,YACA,YACA,UACA,aACA,UACA,UAAU,MACV,eAAe,UACZ;CAEJ,MAAM,QAAQ,eAAe;CAC7B,MAAM,QAAQ,eAAe;CAC7B,MAAM,eACL,OAAO,WAAW,cAAc,OAAO,SAAS,SAAS,gBAAgB,YAAY;CAEtF,IAAI;AACJ,KAAI,aACH,aAAY,MAAM,eAAe,YAAY;EAC5C,WAAW;EACX,cAAc;EACd,eAAe;EACf,CAAC;CAIH,MAAM,WAAW,iBAAiB,KAAK,IAAI;CAC3C,MAAM,QAAQ,OAAO,KAAK,eAAe,UAAU,SAAS;AAE5D,KAAI,CAAC,SAAS,MAAM,OACnB,OAAM,IAAI,mBAAmB;CAG9B,MAAM,SAAS,IAAI,gBAAgB;EAClC,WAAW;EACX,cAAc;EACd;EACA;EACA,eAAe;EACf,MAAM;EACN,UAAU,YAAY;EACtB,uBAAuB;EACvB,CAAC;AACF,KAAI,UAAW,QAAO,IAAI,cAAc,UAAU;CAElD,MAAM,WAAW,GAAG,WAAW,QAAQ,OAAO,GAAG,CAAC,SAAS,OAAO,UAAU;AAG5E,OAAM,SAAS,OAAO;AAEtB,QAAO,IAAI,SAAkB,SAAS,WAAW;EAChD,IAAI,YAAkD;EACtD,IAAI,gBAAuD;EAE3D,MAAM,gBAAgB;AACrB,OAAI,UAAW,cAAa,UAAU;AACtC,OAAI,cAAe,eAAc,cAAc;AAC/C,UAAO,oBAAoB,WAAW,QAAQ;;EAG/C,MAAM,WAAW,UAAwB;AACxC,OAAI,MAAM,WAAW,WAAY;AACjC,OAAI,MAAM,WAAW,MAAO;GAE5B,MAAM,OAAO,MAAM;AACnB,OAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,CAAC,KAAK,KAAM;AAErD,OAAI,KAAK,SAAS,wBAAwB;IACzC,MAAM,MAAM;AACZ,QAAI,IAAI,UAAU,SAAS,IAAI,UAAU,OAAO;AAC/C,cAAS;AACT,YAAO,IAAI,mBAAmB,CAAC;AAC/B;;AAED,QAAI,IAAI,aAAa,UAAU;AAC9B,cAAS;AACT,YAAO,IAAI,mBAAmB,CAAC;AAC/B;;AAED,QAAI,aAAa,IAAI,cAAc,WAAW;AAC7C,cAAS;AACT,YAAO,IAAI,mBAAmB,CAAC;AAC/B;;AAGD,aAAS;AACT,QAAI;AACH,WAAM,OAAO;YACN;AAWR,YAPyB;KACxB,cAAc,IAAI,gBAAgB,IAAI;KACtC,eAAe,IAAI;KACnB,MAAM,IAAI,QAAQ,EAAE;KACpB,YAAY,IAAI,cAAc,KAAK,KAAK,GAAG;KAC3C,GAAI,IAAI,QAAQ,EAAE,MAAM,IAAI,MAAM;KAClC,CACe;cACN,KAAK,SAAS,uBAAuB;IAC/C,MAAM,MAAM;AACZ,aAAS;AACT,QAAI;AACH,WAAM,OAAO;YACN;AAGR,WAAO,IAAI,MAAM,IAAI,SAAS,wBAAwB,CAAC;;;AAIzD,SAAO,iBAAiB,WAAW,QAAQ;AAE3C,cAAY,iBAAiB;AAC5B,YAAS;AACT,OAAI;AACH,UAAM,OAAO;WACN;AAGR,UAAO,IAAI,kBAAkB,CAAC;KAC5B,QAAQ;AAEX,kBAAgB,kBAAkB;AACjC,OAAI,MAAM,QAAQ;AACjB,aAAS;AACT,WAAO,IAAI,kBAAkB,CAAC;;KAE7B,IAAI;GACN"}
|
package/dist/types.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region src/types.d.ts
|
|
2
|
-
/** OAuth provider options for Login with MySocial */
|
|
3
|
-
type AuthProvider = 'google' | 'apple' | 'facebook' | 'twitch';
|
|
2
|
+
/** OAuth provider options for Login with MySocial. Use 'none' for home screen (user picks provider). */
|
|
3
|
+
type AuthProvider = 'google' | 'apple' | 'facebook' | 'twitch' | 'none';
|
|
4
4
|
/** Auth flow mode: popup (opens window) or redirect (navigates away) */
|
|
5
5
|
type AuthMode = 'popup' | 'redirect';
|
|
6
6
|
/** User object returned from exchange/refresh */
|
|
@@ -16,6 +16,7 @@ interface Session {
|
|
|
16
16
|
refresh_token?: string;
|
|
17
17
|
expires_at: number;
|
|
18
18
|
user: AuthUser;
|
|
19
|
+
salt?: string;
|
|
19
20
|
}
|
|
20
21
|
/** Storage adapter for persisting session (memory, sessionStorage, or custom) */
|
|
21
22
|
interface StorageAdapter {
|
|
@@ -39,6 +40,7 @@ interface MySocialAuthConfig {
|
|
|
39
40
|
}
|
|
40
41
|
/** Sign-in options */
|
|
41
42
|
interface SignInOptions {
|
|
43
|
+
/** Provider to use. Default 'none' shows auth home screen. */
|
|
42
44
|
provider?: AuthProvider;
|
|
43
45
|
mode?: AuthMode;
|
|
44
46
|
}
|
package/dist/types.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;KAIY,YAAA;;KAGA,QAAA;;UAGK,QAAA;EAChB,EAAA;EACA,KAAA;EACA,IAAA;EAAA,CACC,GAAA;AAAA;AAJF;AAAA,UAQiB,OAAA;EAChB,YAAA;EACA,aAAA;EACA,UAAA;EACA,IAAA,EAAM,QAAA;AAAA;;
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;KAIY,YAAA;;KAGA,QAAA;;UAGK,QAAA;EAChB,EAAA;EACA,KAAA;EACA,IAAA;EAAA,CACC,GAAA;AAAA;AAJF;AAAA,UAQiB,OAAA;EAChB,YAAA;EACA,aAAA;EACA,UAAA;EACA,IAAA,EAAM,QAAA;EACN,IAAA;AAAA;;UAIgB,cAAA;EAChB,GAAA,CAAI,GAAA;EACJ,GAAA,CAAI,GAAA,UAAa,KAAA;EACjB,MAAA,CAAO,GAAA;AAAA;;KAII,aAAA,0BAAuC,cAAA;;UAGlC,kBAAA;EAChB,UAAA;EACA,UAAA;EACA,QAAA;EACA,WAAA;EACA,OAAA,GAAU,aAAA;EAfoB;EAiB9B,YAAA;EAjB8B;EAmB9B,YAAA;AAAA;;UAIgB,aAAA;EArBC;EAuBjB,QAAA,GAAW,YAAA;EACX,IAAA,GAAO,QAAA;AAAA;;KAII,uBAAA,IAA2B,OAAA,EAAS,OAAA"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@socialproof/mysocial-auth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "MySocial Auth SDK for Login with MySocial via popup or redirect",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
|
-
"author": "
|
|
7
|
+
"author": "The Social Proof Foundation, LLC. <build@socialprooflabs.com>",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./dist/index.mjs",
|
|
10
10
|
"types": "./dist/index.d.mts",
|
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
],
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|
|
25
|
-
"url": "git+https://github.com/
|
|
25
|
+
"url": "git+https://github.com/the-social-proof-foundation/myso-ts-sdks.git"
|
|
26
26
|
},
|
|
27
27
|
"bugs": {
|
|
28
|
-
"url": "https://github.com/
|
|
28
|
+
"url": "https://github.com/the-social-proof-foundation/myso-ts-sdks/issues"
|
|
29
29
|
},
|
|
30
|
-
"homepage": "https://github.com/
|
|
30
|
+
"homepage": "https://github.com/the-social-proof-foundation/myso-ts-sdks/tree/main/packages/mysocial-auth#readme",
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^25.0.8",
|
|
33
33
|
"happy-dom": "^20.1.0",
|