@gosso/client 0.1.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/LICENSE +21 -0
- package/README.md +120 -0
- package/dist/index.d.ts +130 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +633 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on Keep a Changelog, and this project follows Semantic Versioning.
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-06-29
|
|
10
|
+
### Added
|
|
11
|
+
- Initial `@gosso/client` browser SDK for Gosso ordinary SPA clients.
|
|
12
|
+
- Add PKCE redirects, callback handling, token refresh, authenticated `apiFetch`, password login, MFA verification, passkey login and registration, profile management, email changes, MFA settings, passkey management, and session management.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 gosso contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# @gosso/client
|
|
2
|
+
|
|
3
|
+
Browser SDK for Gosso OAuth/OIDC single-page application clients.
|
|
4
|
+
|
|
5
|
+
`@gosso/client` provides the protocol and account self-service layer for ordinary Gosso clients:
|
|
6
|
+
|
|
7
|
+
- Authorization Code + PKCE redirects and callback handling
|
|
8
|
+
- token storage, userinfo loading, logout, and automatic refresh
|
|
9
|
+
- authenticated `apiFetch` with bearer headers and 401 retry
|
|
10
|
+
- username/password login, MFA verification, and passkey login
|
|
11
|
+
- profile, password, email, MFA, passkey, and session management APIs
|
|
12
|
+
|
|
13
|
+
The package intentionally does not ship React UI. Build app-specific pages with your own design system and call the SDK methods underneath.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @gosso/client
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { createGossoClient } from '@gosso/client';
|
|
25
|
+
|
|
26
|
+
export const gossoClient = createGossoClient({
|
|
27
|
+
issuer: window.location.origin,
|
|
28
|
+
clientId: 'blog-spa',
|
|
29
|
+
redirectUri: `${window.location.origin}/callback`,
|
|
30
|
+
scope: 'openid profile email',
|
|
31
|
+
postLoginDefaultPath: '/admin',
|
|
32
|
+
loginPath: '/login',
|
|
33
|
+
storagePrefix: 'my-app',
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Start an OIDC flow:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
await gossoClient.redirectToAuthorize('/admin');
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Handle the callback route:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
const code = new URLSearchParams(location.search).get('code');
|
|
47
|
+
const state = new URLSearchParams(location.search).get('state');
|
|
48
|
+
|
|
49
|
+
if (!code || !state) throw new Error('Missing callback parameters');
|
|
50
|
+
|
|
51
|
+
const { redirectTo } = await gossoClient.handleRedirectCallback(code, state);
|
|
52
|
+
location.href = redirectTo;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Call protected APIs:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
const response = await gossoClient.apiFetch('/api/posts', {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: { 'Content-Type': 'application/json' },
|
|
61
|
+
body: JSON.stringify({ title: 'Hello Gosso' }),
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Password, MFA, and Passkey Login
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const result = await gossoClient.loginWithPassword(username, password);
|
|
69
|
+
|
|
70
|
+
if (result.requires_mfa) {
|
|
71
|
+
await gossoClient.verifyMfa(String(result.mfa_token), code);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
await gossoClient.loginWithPasskey();
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Account Settings
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
await gossoClient.updateProfile('New Display Name');
|
|
83
|
+
await gossoClient.changePassword(currentPassword, newPassword);
|
|
84
|
+
await gossoClient.requestEmailChange(newEmail, password);
|
|
85
|
+
await gossoClient.confirmEmailChange(newEmail, code);
|
|
86
|
+
|
|
87
|
+
const mfaStatus = await gossoClient.getMfaStatus();
|
|
88
|
+
const passkeys = await gossoClient.listPasskeys();
|
|
89
|
+
const sessions = await gossoClient.listSessions();
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Configuration
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
interface GossoClientConfig {
|
|
96
|
+
issuer: string;
|
|
97
|
+
clientId: string;
|
|
98
|
+
redirectUri: string;
|
|
99
|
+
scope: string;
|
|
100
|
+
postLoginDefaultPath: string;
|
|
101
|
+
loginPath: string;
|
|
102
|
+
storagePrefix: string;
|
|
103
|
+
fetchImpl?: typeof fetch;
|
|
104
|
+
onAuthRequired?: () => void;
|
|
105
|
+
onSessionChanged?: (snapshot: SessionSnapshot) => void;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Use a unique `storagePrefix` for each SPA on the same origin to avoid token collisions.
|
|
110
|
+
|
|
111
|
+
## Security Notes
|
|
112
|
+
|
|
113
|
+
- Use Authorization Code + PKCE for browser clients.
|
|
114
|
+
- Serve production clients over HTTPS.
|
|
115
|
+
- Keep the Gosso issuer and app behind a same-origin gateway when possible.
|
|
116
|
+
- Tokens are stored in browser `localStorage` and mirrored to an `access_token` cookie for same-origin Gosso redirects. Treat XSS prevention as part of your security boundary.
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
export interface GossoClientConfig {
|
|
2
|
+
issuer: string;
|
|
3
|
+
clientId: string;
|
|
4
|
+
redirectUri: string;
|
|
5
|
+
scope: string;
|
|
6
|
+
postLoginDefaultPath: string;
|
|
7
|
+
loginPath: string;
|
|
8
|
+
storagePrefix: string;
|
|
9
|
+
fetchImpl?: typeof fetch;
|
|
10
|
+
onAuthRequired?: () => void;
|
|
11
|
+
onSessionChanged?: (snapshot: SessionSnapshot) => void;
|
|
12
|
+
}
|
|
13
|
+
export interface TokenResponse {
|
|
14
|
+
access_token: string;
|
|
15
|
+
refresh_token: string;
|
|
16
|
+
id_token?: string;
|
|
17
|
+
expires_in: number;
|
|
18
|
+
}
|
|
19
|
+
export interface UserProfile {
|
|
20
|
+
sub: string;
|
|
21
|
+
name?: string;
|
|
22
|
+
preferred_username?: string;
|
|
23
|
+
email?: string;
|
|
24
|
+
roles?: string[];
|
|
25
|
+
scope?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface SessionSnapshot {
|
|
28
|
+
accessToken: string | null;
|
|
29
|
+
refreshToken: string | null;
|
|
30
|
+
profile: UserProfile | null;
|
|
31
|
+
loggedIn: boolean;
|
|
32
|
+
isAdmin: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface LoginResult {
|
|
35
|
+
access_token?: string;
|
|
36
|
+
refresh_token?: string;
|
|
37
|
+
expires_in?: number;
|
|
38
|
+
requires_mfa?: boolean;
|
|
39
|
+
mfa_token?: string;
|
|
40
|
+
[key: string]: unknown;
|
|
41
|
+
}
|
|
42
|
+
export interface MfaStatus {
|
|
43
|
+
enabled: boolean;
|
|
44
|
+
types: string[];
|
|
45
|
+
}
|
|
46
|
+
export interface MfaEnrollment {
|
|
47
|
+
secret: string;
|
|
48
|
+
otpauth_url: string;
|
|
49
|
+
}
|
|
50
|
+
export interface SessionInfo {
|
|
51
|
+
id: string;
|
|
52
|
+
ip: string;
|
|
53
|
+
user_agent: string;
|
|
54
|
+
created_at: string;
|
|
55
|
+
last_active_at: string;
|
|
56
|
+
}
|
|
57
|
+
export interface PasskeyInfo {
|
|
58
|
+
id: string;
|
|
59
|
+
name: string;
|
|
60
|
+
created_at?: string;
|
|
61
|
+
}
|
|
62
|
+
export declare function bufferToBase64URL(buffer: ArrayBuffer): string;
|
|
63
|
+
export declare function base64URLToBuffer(base64URLString: string): ArrayBuffer;
|
|
64
|
+
export declare function createGossoClient(inputConfig: GossoClientConfig): {
|
|
65
|
+
config: {
|
|
66
|
+
issuer: string;
|
|
67
|
+
clientId: string;
|
|
68
|
+
redirectUri: string;
|
|
69
|
+
scope: string;
|
|
70
|
+
postLoginDefaultPath: string;
|
|
71
|
+
loginPath: string;
|
|
72
|
+
storagePrefix: string;
|
|
73
|
+
fetchImpl?: typeof fetch;
|
|
74
|
+
onAuthRequired?: () => void;
|
|
75
|
+
onSessionChanged?: (snapshot: SessionSnapshot) => void;
|
|
76
|
+
};
|
|
77
|
+
storageKeys: {
|
|
78
|
+
accessToken: string;
|
|
79
|
+
refreshToken: string;
|
|
80
|
+
userProfile: string;
|
|
81
|
+
pkceVerifier: string;
|
|
82
|
+
authState: string;
|
|
83
|
+
postLoginRedirect: string;
|
|
84
|
+
tokenIssuedAt: string;
|
|
85
|
+
tokenExpiresIn: string;
|
|
86
|
+
refreshLock: string;
|
|
87
|
+
};
|
|
88
|
+
getAccessToken: () => string | null;
|
|
89
|
+
getRefreshToken: () => string | null;
|
|
90
|
+
getUserProfile: () => UserProfile | null;
|
|
91
|
+
getSnapshot: () => SessionSnapshot;
|
|
92
|
+
isLoggedIn: () => boolean;
|
|
93
|
+
isAdmin: () => boolean;
|
|
94
|
+
saveTokenSet: (data: TokenResponse | {
|
|
95
|
+
access_token: string;
|
|
96
|
+
refresh_token?: string;
|
|
97
|
+
expires_in?: number;
|
|
98
|
+
}) => void;
|
|
99
|
+
clear: () => void;
|
|
100
|
+
logout: (redirectTo?: string) => Promise<void>;
|
|
101
|
+
redirectToAuthorize: (customRedirectUri?: string) => Promise<void>;
|
|
102
|
+
exchangeCodeForToken: (code: string, state: string) => Promise<TokenResponse>;
|
|
103
|
+
handleRedirectCallback: (code: string, state: string) => Promise<{
|
|
104
|
+
tokenSet: TokenResponse;
|
|
105
|
+
redirectTo: string;
|
|
106
|
+
}>;
|
|
107
|
+
fetchUserProfile: (accessToken?: string | null) => Promise<UserProfile>;
|
|
108
|
+
refreshAccessToken: () => Promise<string>;
|
|
109
|
+
apiFetch: (url: string, options?: RequestInit) => Promise<Response>;
|
|
110
|
+
loginWithPassword: (username: string, password: string) => Promise<LoginResult>;
|
|
111
|
+
verifyMfa: (mfaToken: string, code: string, type?: "totp" | "passkey") => Promise<TokenResponse>;
|
|
112
|
+
loginWithPasskey: () => Promise<TokenResponse>;
|
|
113
|
+
updateProfile: (displayName: string) => Promise<UserProfile>;
|
|
114
|
+
changePassword: (currentPassword: string, newPassword: string) => Promise<void>;
|
|
115
|
+
requestEmailChange: (newEmail: string, password: string) => Promise<void>;
|
|
116
|
+
confirmEmailChange: (newEmail: string, code: string) => Promise<UserProfile>;
|
|
117
|
+
getMfaStatus: () => Promise<MfaStatus>;
|
|
118
|
+
enrollMfa: () => Promise<MfaEnrollment>;
|
|
119
|
+
activateMfa: (code: string) => Promise<string[]>;
|
|
120
|
+
disableMfa: (currentPassword: string) => Promise<void>;
|
|
121
|
+
generateBackupCodes: () => Promise<string[]>;
|
|
122
|
+
listPasskeys: () => Promise<PasskeyInfo[]>;
|
|
123
|
+
registerPasskey: (name: string) => Promise<void>;
|
|
124
|
+
deletePasskey: (id: string) => Promise<void>;
|
|
125
|
+
listSessions: () => Promise<SessionInfo[]>;
|
|
126
|
+
getCurrentSession: () => Promise<SessionInfo>;
|
|
127
|
+
revokeSession: (id: string) => Promise<void>;
|
|
128
|
+
};
|
|
129
|
+
export type GossoClient = ReturnType<typeof createGossoClient>;
|
|
130
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
const REFRESH_LOCK_TTL_MS = 15_000;
|
|
2
|
+
const REFRESH_WAIT_TIMEOUT_MS = 20_000;
|
|
3
|
+
const REFRESH_WAIT_POLL_MS = 100;
|
|
4
|
+
const REFRESH_WEB_LOCK_NAME = 'gosso-auth-refresh';
|
|
5
|
+
const defaultConfig = {
|
|
6
|
+
scope: 'openid profile email',
|
|
7
|
+
postLoginDefaultPath: '/',
|
|
8
|
+
loginPath: '/login',
|
|
9
|
+
storagePrefix: 'gosso',
|
|
10
|
+
};
|
|
11
|
+
function normalizeBaseUrl(url) {
|
|
12
|
+
return url.replace(/\/+$/, '');
|
|
13
|
+
}
|
|
14
|
+
function generateRandomString(length) {
|
|
15
|
+
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
|
|
16
|
+
let text = '';
|
|
17
|
+
for (let i = 0; i < length; i += 1) {
|
|
18
|
+
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
|
19
|
+
}
|
|
20
|
+
return text;
|
|
21
|
+
}
|
|
22
|
+
async function generateCodeChallenge(verifier) {
|
|
23
|
+
const data = new TextEncoder().encode(verifier);
|
|
24
|
+
const digest = await window.crypto.subtle.digest('SHA-256', data);
|
|
25
|
+
return bufferToBase64URL(digest);
|
|
26
|
+
}
|
|
27
|
+
function cookieSecureAttribute() {
|
|
28
|
+
return location.protocol === 'https:' ? '; Secure' : '';
|
|
29
|
+
}
|
|
30
|
+
function readClaimsFromAccessToken(accessToken) {
|
|
31
|
+
try {
|
|
32
|
+
const payloadBase64 = accessToken.split('.')[1];
|
|
33
|
+
const base64 = payloadBase64.replace(/-/g, '+').replace(/_/g, '/');
|
|
34
|
+
const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);
|
|
35
|
+
return JSON.parse(decodeURIComponent(escape(atob(padded))));
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function readRolesFromAccessToken(accessToken) {
|
|
42
|
+
const payload = readClaimsFromAccessToken(accessToken);
|
|
43
|
+
return Array.isArray(payload?.roles) ? payload.roles : undefined;
|
|
44
|
+
}
|
|
45
|
+
function readScopeFromAccessToken(accessToken) {
|
|
46
|
+
const payload = readClaimsFromAccessToken(accessToken);
|
|
47
|
+
return typeof payload?.scope === 'string' ? payload.scope : undefined;
|
|
48
|
+
}
|
|
49
|
+
function hasAdminAccess(profile, accessToken) {
|
|
50
|
+
const hasAdminRole = profile?.roles?.includes('admin') || false;
|
|
51
|
+
const scope = accessToken ? readScopeFromAccessToken(accessToken) : profile?.scope;
|
|
52
|
+
return hasAdminRole && Boolean(scope?.split(/\s+/).includes('admin'));
|
|
53
|
+
}
|
|
54
|
+
function parseRefreshLock(raw) {
|
|
55
|
+
if (!raw)
|
|
56
|
+
return null;
|
|
57
|
+
try {
|
|
58
|
+
const lock = JSON.parse(raw);
|
|
59
|
+
if (typeof lock.owner !== 'string' || typeof lock.expiresAt !== 'number') {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return { owner: lock.owner, expiresAt: lock.expiresAt };
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function generateRefreshOwner() {
|
|
69
|
+
if (window.crypto?.randomUUID) {
|
|
70
|
+
return window.crypto.randomUUID();
|
|
71
|
+
}
|
|
72
|
+
return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
73
|
+
}
|
|
74
|
+
async function parseJsonEnvelope(response, fallbackMessage) {
|
|
75
|
+
const body = (await response.json());
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
throw new Error(body.message || fallbackMessage);
|
|
78
|
+
}
|
|
79
|
+
return body.data;
|
|
80
|
+
}
|
|
81
|
+
export function bufferToBase64URL(buffer) {
|
|
82
|
+
const bytes = new Uint8Array(buffer);
|
|
83
|
+
let binary = '';
|
|
84
|
+
for (let i = 0; i < bytes.byteLength; i += 1) {
|
|
85
|
+
binary += String.fromCharCode(bytes[i]);
|
|
86
|
+
}
|
|
87
|
+
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
88
|
+
}
|
|
89
|
+
export function base64URLToBuffer(base64URLString) {
|
|
90
|
+
const base64 = base64URLString.replace(/-/g, '+').replace(/_/g, '/');
|
|
91
|
+
const padded = base64 + '='.repeat((4 - (base64.length % 4)) % 4);
|
|
92
|
+
const binary = atob(padded);
|
|
93
|
+
const bytes = new Uint8Array(binary.length);
|
|
94
|
+
for (let i = 0; i < binary.length; i += 1) {
|
|
95
|
+
bytes[i] = binary.charCodeAt(i);
|
|
96
|
+
}
|
|
97
|
+
return bytes.buffer;
|
|
98
|
+
}
|
|
99
|
+
export function createGossoClient(inputConfig) {
|
|
100
|
+
const config = {
|
|
101
|
+
...defaultConfig,
|
|
102
|
+
...inputConfig,
|
|
103
|
+
issuer: normalizeBaseUrl(inputConfig.issuer),
|
|
104
|
+
};
|
|
105
|
+
const fetcher = config.fetchImpl || fetch.bind(window);
|
|
106
|
+
const key = (name) => `${config.storagePrefix}:${name}`;
|
|
107
|
+
const storageKeys = {
|
|
108
|
+
accessToken: key('access_token'),
|
|
109
|
+
refreshToken: key('refresh_token'),
|
|
110
|
+
userProfile: key('user_profile'),
|
|
111
|
+
pkceVerifier: key('pkce_verifier'),
|
|
112
|
+
authState: key('auth_state'),
|
|
113
|
+
postLoginRedirect: key('post_login_redirect'),
|
|
114
|
+
tokenIssuedAt: key('token_issued_at'),
|
|
115
|
+
tokenExpiresIn: key('token_expires_in'),
|
|
116
|
+
refreshLock: key('auth_refresh_lock'),
|
|
117
|
+
};
|
|
118
|
+
let refreshPromise = null;
|
|
119
|
+
const setCookie = (name, value, maxAgeSeconds) => {
|
|
120
|
+
document.cookie = `${name}=${value}; path=/; max-age=${maxAgeSeconds}; SameSite=Lax${cookieSecureAttribute()}`;
|
|
121
|
+
};
|
|
122
|
+
const deleteCookie = (name) => {
|
|
123
|
+
document.cookie = `${name}=; path=/; max-age=-1; SameSite=Lax`;
|
|
124
|
+
};
|
|
125
|
+
const readProfile = () => {
|
|
126
|
+
const profile = localStorage.getItem(storageKeys.userProfile);
|
|
127
|
+
if (!profile)
|
|
128
|
+
return null;
|
|
129
|
+
try {
|
|
130
|
+
return JSON.parse(profile);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
const getAccessToken = () => localStorage.getItem(storageKeys.accessToken);
|
|
137
|
+
const getRefreshToken = () => localStorage.getItem(storageKeys.refreshToken);
|
|
138
|
+
const getSnapshot = () => {
|
|
139
|
+
const accessToken = getAccessToken();
|
|
140
|
+
const refreshToken = getRefreshToken();
|
|
141
|
+
const profile = readProfile();
|
|
142
|
+
return {
|
|
143
|
+
accessToken,
|
|
144
|
+
refreshToken,
|
|
145
|
+
profile,
|
|
146
|
+
loggedIn: Boolean(accessToken),
|
|
147
|
+
isAdmin: hasAdminAccess(profile, accessToken),
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
const emitSessionChanged = () => {
|
|
151
|
+
config.onSessionChanged?.(getSnapshot());
|
|
152
|
+
};
|
|
153
|
+
const saveTokenSet = (data) => {
|
|
154
|
+
localStorage.setItem(storageKeys.accessToken, data.access_token);
|
|
155
|
+
if (data.refresh_token) {
|
|
156
|
+
localStorage.setItem(storageKeys.refreshToken, data.refresh_token);
|
|
157
|
+
}
|
|
158
|
+
localStorage.setItem(storageKeys.tokenIssuedAt, String(Date.now()));
|
|
159
|
+
localStorage.setItem(storageKeys.tokenExpiresIn, String(data.expires_in || 900));
|
|
160
|
+
setCookie('access_token', data.access_token, data.expires_in || 900);
|
|
161
|
+
emitSessionChanged();
|
|
162
|
+
};
|
|
163
|
+
const clear = () => {
|
|
164
|
+
Object.values(storageKeys).forEach((storageKey) => localStorage.removeItem(storageKey));
|
|
165
|
+
deleteCookie('access_token');
|
|
166
|
+
emitSessionChanged();
|
|
167
|
+
};
|
|
168
|
+
const redirectToLogin = () => {
|
|
169
|
+
if (config.onAuthRequired) {
|
|
170
|
+
config.onAuthRequired();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
window.location.href = config.loginPath;
|
|
174
|
+
};
|
|
175
|
+
const tryAcquireRefreshLock = (owner) => {
|
|
176
|
+
const now = Date.now();
|
|
177
|
+
const current = parseRefreshLock(localStorage.getItem(storageKeys.refreshLock));
|
|
178
|
+
if (current && current.expiresAt > now && current.owner !== owner) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
const nextLock = { owner, expiresAt: now + REFRESH_LOCK_TTL_MS };
|
|
182
|
+
localStorage.setItem(storageKeys.refreshLock, JSON.stringify(nextLock));
|
|
183
|
+
return parseRefreshLock(localStorage.getItem(storageKeys.refreshLock))?.owner === owner;
|
|
184
|
+
};
|
|
185
|
+
const releaseRefreshLock = (owner) => {
|
|
186
|
+
const current = parseRefreshLock(localStorage.getItem(storageKeys.refreshLock));
|
|
187
|
+
if (!current || current.owner === owner || current.expiresAt <= Date.now()) {
|
|
188
|
+
localStorage.removeItem(storageKeys.refreshLock);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
const waitForRefreshFromAnotherContext = (previousRefreshToken) => {
|
|
192
|
+
const startedAt = Date.now();
|
|
193
|
+
return new Promise((resolve) => {
|
|
194
|
+
let settled = false;
|
|
195
|
+
let intervalId;
|
|
196
|
+
let timeoutId;
|
|
197
|
+
const finish = (token) => {
|
|
198
|
+
if (settled)
|
|
199
|
+
return;
|
|
200
|
+
settled = true;
|
|
201
|
+
if (intervalId !== undefined)
|
|
202
|
+
window.clearInterval(intervalId);
|
|
203
|
+
if (timeoutId !== undefined)
|
|
204
|
+
window.clearTimeout(timeoutId);
|
|
205
|
+
window.removeEventListener('storage', check);
|
|
206
|
+
resolve(token);
|
|
207
|
+
};
|
|
208
|
+
const check = () => {
|
|
209
|
+
const accessToken = getAccessToken();
|
|
210
|
+
const refreshToken = getRefreshToken();
|
|
211
|
+
const issuedAt = Number(localStorage.getItem(storageKeys.tokenIssuedAt));
|
|
212
|
+
if (accessToken && refreshToken && refreshToken !== previousRefreshToken && issuedAt >= startedAt) {
|
|
213
|
+
finish(accessToken);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const lock = parseRefreshLock(localStorage.getItem(storageKeys.refreshLock));
|
|
217
|
+
if (!lock || lock.expiresAt <= Date.now()) {
|
|
218
|
+
finish(null);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
window.addEventListener('storage', check);
|
|
222
|
+
intervalId = window.setInterval(check, REFRESH_WAIT_POLL_MS);
|
|
223
|
+
timeoutId = window.setTimeout(() => finish(null), REFRESH_WAIT_TIMEOUT_MS);
|
|
224
|
+
check();
|
|
225
|
+
});
|
|
226
|
+
};
|
|
227
|
+
const requestBrowserRefreshLock = async (callback) => {
|
|
228
|
+
const locks = navigator.locks;
|
|
229
|
+
if (!locks)
|
|
230
|
+
return callback();
|
|
231
|
+
return locks.request(REFRESH_WEB_LOCK_NAME, { mode: 'exclusive' }, callback);
|
|
232
|
+
};
|
|
233
|
+
const performTokenRefresh = async (previousRefreshToken) => {
|
|
234
|
+
const latestRefreshToken = getRefreshToken();
|
|
235
|
+
if (!latestRefreshToken)
|
|
236
|
+
throw new Error('No refresh token found');
|
|
237
|
+
if (latestRefreshToken !== previousRefreshToken) {
|
|
238
|
+
const latestAccessToken = getAccessToken();
|
|
239
|
+
if (latestAccessToken)
|
|
240
|
+
return latestAccessToken;
|
|
241
|
+
}
|
|
242
|
+
const response = await fetcher(`${config.issuer}/api/v1/auth/refresh`, {
|
|
243
|
+
method: 'POST',
|
|
244
|
+
headers: { 'Content-Type': 'application/json' },
|
|
245
|
+
body: JSON.stringify({ refresh_token: latestRefreshToken }),
|
|
246
|
+
});
|
|
247
|
+
const data = await parseJsonEnvelope(response, 'Token refresh failed');
|
|
248
|
+
saveTokenSet(data);
|
|
249
|
+
return data.access_token;
|
|
250
|
+
};
|
|
251
|
+
const refreshAccessToken = async () => {
|
|
252
|
+
if (refreshPromise)
|
|
253
|
+
return refreshPromise;
|
|
254
|
+
refreshPromise = (async () => {
|
|
255
|
+
const owner = generateRefreshOwner();
|
|
256
|
+
let lockAcquired = false;
|
|
257
|
+
try {
|
|
258
|
+
const refreshToken = getRefreshToken();
|
|
259
|
+
if (!refreshToken)
|
|
260
|
+
throw new Error('No refresh token found');
|
|
261
|
+
if (navigator.locks) {
|
|
262
|
+
return requestBrowserRefreshLock(() => performTokenRefresh(refreshToken));
|
|
263
|
+
}
|
|
264
|
+
lockAcquired = tryAcquireRefreshLock(owner);
|
|
265
|
+
if (!lockAcquired) {
|
|
266
|
+
const tokenFromPeer = await waitForRefreshFromAnotherContext(refreshToken);
|
|
267
|
+
if (tokenFromPeer)
|
|
268
|
+
return tokenFromPeer;
|
|
269
|
+
lockAcquired = tryAcquireRefreshLock(owner);
|
|
270
|
+
if (!lockAcquired)
|
|
271
|
+
throw new Error('Token refresh is already in progress');
|
|
272
|
+
}
|
|
273
|
+
return performTokenRefresh(refreshToken);
|
|
274
|
+
}
|
|
275
|
+
finally {
|
|
276
|
+
if (lockAcquired)
|
|
277
|
+
releaseRefreshLock(owner);
|
|
278
|
+
refreshPromise = null;
|
|
279
|
+
}
|
|
280
|
+
})();
|
|
281
|
+
return refreshPromise;
|
|
282
|
+
};
|
|
283
|
+
const fetchUserProfile = async (accessToken = getAccessToken()) => {
|
|
284
|
+
if (!accessToken)
|
|
285
|
+
throw new Error('No access token found');
|
|
286
|
+
const response = await fetcher(`${config.issuer}/oidc/userinfo`, {
|
|
287
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
288
|
+
});
|
|
289
|
+
if (!response.ok)
|
|
290
|
+
throw new Error('Failed to fetch user profile');
|
|
291
|
+
const data = (await response.json());
|
|
292
|
+
const roles = readRolesFromAccessToken(accessToken);
|
|
293
|
+
if (roles)
|
|
294
|
+
data.roles = roles;
|
|
295
|
+
const scope = readScopeFromAccessToken(accessToken);
|
|
296
|
+
if (scope)
|
|
297
|
+
data.scope = scope;
|
|
298
|
+
localStorage.setItem(storageKeys.userProfile, JSON.stringify(data));
|
|
299
|
+
emitSessionChanged();
|
|
300
|
+
return data;
|
|
301
|
+
};
|
|
302
|
+
const apiFetch = async (url, options = {}) => {
|
|
303
|
+
let token = getAccessToken();
|
|
304
|
+
if (!token) {
|
|
305
|
+
redirectToLogin();
|
|
306
|
+
return new Response(null, { status: 401 });
|
|
307
|
+
}
|
|
308
|
+
const issuedAt = Number(localStorage.getItem(storageKeys.tokenIssuedAt));
|
|
309
|
+
const expiresIn = Number(localStorage.getItem(storageKeys.tokenExpiresIn)) || 900;
|
|
310
|
+
if (issuedAt && Date.now() - issuedAt > expiresIn * 1000) {
|
|
311
|
+
try {
|
|
312
|
+
token = await refreshAccessToken();
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
clear();
|
|
316
|
+
redirectToLogin();
|
|
317
|
+
return new Response(null, { status: 401 });
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
const headers = new Headers(options.headers || {});
|
|
321
|
+
if (token && !headers.has('Authorization')) {
|
|
322
|
+
headers.set('Authorization', `Bearer ${token}`);
|
|
323
|
+
}
|
|
324
|
+
let response = await fetcher(url, { ...options, headers });
|
|
325
|
+
if (response.status === 401 && getRefreshToken()) {
|
|
326
|
+
try {
|
|
327
|
+
const freshToken = await refreshAccessToken();
|
|
328
|
+
headers.set('Authorization', `Bearer ${freshToken}`);
|
|
329
|
+
response = await fetcher(url, { ...options, headers });
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
clear();
|
|
333
|
+
redirectToLogin();
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return response;
|
|
337
|
+
};
|
|
338
|
+
const redirectToAuthorize = async (customRedirectUri) => {
|
|
339
|
+
const verifier = generateRandomString(64);
|
|
340
|
+
const state = generateRandomString(16);
|
|
341
|
+
localStorage.setItem(storageKeys.pkceVerifier, verifier);
|
|
342
|
+
localStorage.setItem(storageKeys.authState, state);
|
|
343
|
+
if (customRedirectUri) {
|
|
344
|
+
localStorage.setItem(storageKeys.postLoginRedirect, customRedirectUri);
|
|
345
|
+
}
|
|
346
|
+
const challenge = await generateCodeChallenge(verifier);
|
|
347
|
+
const authUrl = new URL(`${config.issuer}/oauth2/authorize`);
|
|
348
|
+
authUrl.searchParams.append('client_id', config.clientId);
|
|
349
|
+
authUrl.searchParams.append('response_type', 'code');
|
|
350
|
+
authUrl.searchParams.append('redirect_uri', config.redirectUri);
|
|
351
|
+
authUrl.searchParams.append('scope', config.scope);
|
|
352
|
+
authUrl.searchParams.append('code_challenge', challenge);
|
|
353
|
+
authUrl.searchParams.append('code_challenge_method', 'S256');
|
|
354
|
+
authUrl.searchParams.append('state', state);
|
|
355
|
+
window.location.href = authUrl.toString();
|
|
356
|
+
};
|
|
357
|
+
const exchangeCodeForToken = async (code, state) => {
|
|
358
|
+
const savedState = localStorage.getItem(storageKeys.authState);
|
|
359
|
+
const verifier = localStorage.getItem(storageKeys.pkceVerifier);
|
|
360
|
+
if (state !== savedState)
|
|
361
|
+
throw new Error('State mismatch. Potential CSRF attack.');
|
|
362
|
+
if (!verifier)
|
|
363
|
+
throw new Error('PKCE verifier not found. Authentication flow expired.');
|
|
364
|
+
const body = new URLSearchParams();
|
|
365
|
+
body.append('grant_type', 'authorization_code');
|
|
366
|
+
body.append('client_id', config.clientId);
|
|
367
|
+
body.append('code', code);
|
|
368
|
+
body.append('code_verifier', verifier);
|
|
369
|
+
body.append('redirect_uri', config.redirectUri);
|
|
370
|
+
const response = await fetcher(`${config.issuer}/oauth2/token`, {
|
|
371
|
+
method: 'POST',
|
|
372
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
373
|
+
body: body.toString(),
|
|
374
|
+
});
|
|
375
|
+
if (!response.ok) {
|
|
376
|
+
throw new Error(`Token exchange failed: ${await response.text()}`);
|
|
377
|
+
}
|
|
378
|
+
const data = (await response.json());
|
|
379
|
+
saveTokenSet(data);
|
|
380
|
+
localStorage.removeItem(storageKeys.pkceVerifier);
|
|
381
|
+
localStorage.removeItem(storageKeys.authState);
|
|
382
|
+
return data;
|
|
383
|
+
};
|
|
384
|
+
const handleRedirectCallback = async (code, state) => {
|
|
385
|
+
const tokenSet = await exchangeCodeForToken(code, state);
|
|
386
|
+
await fetchUserProfile(tokenSet.access_token);
|
|
387
|
+
const redirectTo = localStorage.getItem(storageKeys.postLoginRedirect) || config.postLoginDefaultPath;
|
|
388
|
+
localStorage.removeItem(storageKeys.postLoginRedirect);
|
|
389
|
+
return { tokenSet, redirectTo };
|
|
390
|
+
};
|
|
391
|
+
const logout = async (redirectTo = '/') => {
|
|
392
|
+
const accessToken = getAccessToken();
|
|
393
|
+
try {
|
|
394
|
+
if (accessToken) {
|
|
395
|
+
await fetcher(`${config.issuer}/api/v1/auth/logout`, {
|
|
396
|
+
method: 'POST',
|
|
397
|
+
headers: { Authorization: `Bearer ${accessToken}` },
|
|
398
|
+
credentials: 'same-origin',
|
|
399
|
+
keepalive: true,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
finally {
|
|
404
|
+
clear();
|
|
405
|
+
window.location.href = redirectTo;
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
const loginWithPassword = async (username, password) => {
|
|
409
|
+
const response = await fetcher(`${config.issuer}/api/v1/auth/login`, {
|
|
410
|
+
method: 'POST',
|
|
411
|
+
headers: { 'Content-Type': 'application/json' },
|
|
412
|
+
body: JSON.stringify({ username, password }),
|
|
413
|
+
});
|
|
414
|
+
const result = await parseJsonEnvelope(response, 'Login failed');
|
|
415
|
+
if (result.access_token) {
|
|
416
|
+
saveTokenSet(result);
|
|
417
|
+
await fetchUserProfile(result.access_token);
|
|
418
|
+
}
|
|
419
|
+
return result;
|
|
420
|
+
};
|
|
421
|
+
const verifyMfa = async (mfaToken, code, type = 'totp') => {
|
|
422
|
+
const response = await fetcher(`${config.issuer}/api/v1/auth/mfa/verify`, {
|
|
423
|
+
method: 'POST',
|
|
424
|
+
headers: { 'Content-Type': 'application/json' },
|
|
425
|
+
body: JSON.stringify({ mfa_token: mfaToken, code, type }),
|
|
426
|
+
});
|
|
427
|
+
const data = await parseJsonEnvelope(response, 'MFA verification failed');
|
|
428
|
+
saveTokenSet(data);
|
|
429
|
+
await fetchUserProfile(data.access_token);
|
|
430
|
+
return data;
|
|
431
|
+
};
|
|
432
|
+
const loginWithPasskey = async () => {
|
|
433
|
+
const beginRes = await fetcher(`${config.issuer}/api/v1/passkey/login/begin`, {
|
|
434
|
+
method: 'POST',
|
|
435
|
+
headers: { 'Content-Type': 'application/json' },
|
|
436
|
+
body: JSON.stringify({}),
|
|
437
|
+
});
|
|
438
|
+
const begin = await parseJsonEnvelope(beginRes, 'Failed to begin passkey login');
|
|
439
|
+
const options = {
|
|
440
|
+
...begin.options,
|
|
441
|
+
challenge: base64URLToBuffer(begin.options.challenge),
|
|
442
|
+
allowCredentials: (begin.options.allowCredentials || []).map((cred) => ({
|
|
443
|
+
...cred,
|
|
444
|
+
id: base64URLToBuffer(cred.id),
|
|
445
|
+
})),
|
|
446
|
+
};
|
|
447
|
+
const assertion = (await navigator.credentials.get({ publicKey: options }));
|
|
448
|
+
if (!assertion?.response)
|
|
449
|
+
throw new Error('Passkey authentication cancelled or failed');
|
|
450
|
+
const assertionResponse = assertion.response;
|
|
451
|
+
const completeRes = await fetcher(`${config.issuer}/api/v1/passkey/login/complete`, {
|
|
452
|
+
method: 'POST',
|
|
453
|
+
headers: { 'Content-Type': 'application/json' },
|
|
454
|
+
body: JSON.stringify({
|
|
455
|
+
request_id: begin.request_id,
|
|
456
|
+
id: assertion.id,
|
|
457
|
+
rawId: bufferToBase64URL(assertion.rawId),
|
|
458
|
+
type: assertion.type,
|
|
459
|
+
response: {
|
|
460
|
+
clientDataJSON: bufferToBase64URL(assertionResponse.clientDataJSON),
|
|
461
|
+
authenticatorData: bufferToBase64URL(assertionResponse.authenticatorData),
|
|
462
|
+
signature: bufferToBase64URL(assertionResponse.signature),
|
|
463
|
+
userHandle: assertionResponse.userHandle ? bufferToBase64URL(assertionResponse.userHandle) : null,
|
|
464
|
+
},
|
|
465
|
+
}),
|
|
466
|
+
});
|
|
467
|
+
const data = await parseJsonEnvelope(completeRes, 'Passkey login failed');
|
|
468
|
+
saveTokenSet(data);
|
|
469
|
+
await fetchUserProfile(data.access_token);
|
|
470
|
+
return data;
|
|
471
|
+
};
|
|
472
|
+
const updateProfile = async (displayName) => {
|
|
473
|
+
const response = await apiFetch(`${config.issuer}/api/v1/auth/profile`, {
|
|
474
|
+
method: 'PUT',
|
|
475
|
+
headers: { 'Content-Type': 'application/json' },
|
|
476
|
+
body: JSON.stringify({ display_name: displayName }),
|
|
477
|
+
});
|
|
478
|
+
await parseJsonEnvelope(response, 'Failed to update profile');
|
|
479
|
+
return fetchUserProfile();
|
|
480
|
+
};
|
|
481
|
+
const changePassword = async (currentPassword, newPassword) => {
|
|
482
|
+
const response = await apiFetch(`${config.issuer}/api/v1/auth/password/change`, {
|
|
483
|
+
method: 'POST',
|
|
484
|
+
headers: { 'Content-Type': 'application/json' },
|
|
485
|
+
body: JSON.stringify({ current_password: currentPassword, new_password: newPassword }),
|
|
486
|
+
});
|
|
487
|
+
await parseJsonEnvelope(response, 'Failed to change password');
|
|
488
|
+
};
|
|
489
|
+
const requestEmailChange = async (newEmail, password) => {
|
|
490
|
+
const response = await apiFetch(`${config.issuer}/api/v1/auth/profile/email/change/request`, {
|
|
491
|
+
method: 'POST',
|
|
492
|
+
headers: { 'Content-Type': 'application/json' },
|
|
493
|
+
body: JSON.stringify({ new_email: newEmail, password }),
|
|
494
|
+
});
|
|
495
|
+
await parseJsonEnvelope(response, 'Failed to request email verification code');
|
|
496
|
+
};
|
|
497
|
+
const confirmEmailChange = async (newEmail, code) => {
|
|
498
|
+
const response = await apiFetch(`${config.issuer}/api/v1/auth/profile/email/change/confirm`, {
|
|
499
|
+
method: 'POST',
|
|
500
|
+
headers: { 'Content-Type': 'application/json' },
|
|
501
|
+
body: JSON.stringify({ new_email: newEmail, code }),
|
|
502
|
+
});
|
|
503
|
+
await parseJsonEnvelope(response, 'Failed to confirm email change');
|
|
504
|
+
return fetchUserProfile();
|
|
505
|
+
};
|
|
506
|
+
const getMfaStatus = async () => {
|
|
507
|
+
const response = await apiFetch(`${config.issuer}/api/v1/auth/mfa`);
|
|
508
|
+
return parseJsonEnvelope(response, 'Failed to load MFA status');
|
|
509
|
+
};
|
|
510
|
+
const enrollMfa = async () => {
|
|
511
|
+
const response = await apiFetch(`${config.issuer}/api/v1/auth/mfa/enroll`, { method: 'POST' });
|
|
512
|
+
return parseJsonEnvelope(response, 'Failed to enroll MFA');
|
|
513
|
+
};
|
|
514
|
+
const activateMfa = async (code) => {
|
|
515
|
+
const response = await apiFetch(`${config.issuer}/api/v1/auth/mfa/activate`, {
|
|
516
|
+
method: 'POST',
|
|
517
|
+
headers: { 'Content-Type': 'application/json' },
|
|
518
|
+
body: JSON.stringify({ code }),
|
|
519
|
+
});
|
|
520
|
+
await parseJsonEnvelope(response, 'Failed to activate MFA');
|
|
521
|
+
const codesResponse = await apiFetch(`${config.issuer}/api/v1/auth/mfa/backup-codes`, { method: 'POST' });
|
|
522
|
+
const data = await parseJsonEnvelope(codesResponse, 'Failed to generate backup codes');
|
|
523
|
+
return data.backup_codes || [];
|
|
524
|
+
};
|
|
525
|
+
const disableMfa = async (currentPassword) => {
|
|
526
|
+
const response = await apiFetch(`${config.issuer}/api/v1/auth/mfa`, {
|
|
527
|
+
method: 'DELETE',
|
|
528
|
+
headers: { 'Content-Type': 'application/json' },
|
|
529
|
+
body: JSON.stringify({ current_password: currentPassword }),
|
|
530
|
+
});
|
|
531
|
+
await parseJsonEnvelope(response, 'Failed to disable MFA');
|
|
532
|
+
};
|
|
533
|
+
const generateBackupCodes = async () => {
|
|
534
|
+
const response = await apiFetch(`${config.issuer}/api/v1/auth/mfa/backup-codes`, { method: 'POST' });
|
|
535
|
+
const data = await parseJsonEnvelope(response, 'Failed to generate backup codes');
|
|
536
|
+
return data.backup_codes || [];
|
|
537
|
+
};
|
|
538
|
+
const listPasskeys = async () => {
|
|
539
|
+
const response = await apiFetch(`${config.issuer}/api/v1/passkeys`);
|
|
540
|
+
return parseJsonEnvelope(response, 'Failed to load passkeys');
|
|
541
|
+
};
|
|
542
|
+
const registerPasskey = async (name) => {
|
|
543
|
+
const beginRes = await apiFetch(`${config.issuer}/api/v1/passkey/register/begin`, { method: 'POST' });
|
|
544
|
+
const begin = await parseJsonEnvelope(beginRes, 'Failed to initialize passkey registration');
|
|
545
|
+
const options = {
|
|
546
|
+
...begin.options,
|
|
547
|
+
challenge: base64URLToBuffer(begin.options.challenge),
|
|
548
|
+
user: {
|
|
549
|
+
...begin.options.user,
|
|
550
|
+
id: base64URLToBuffer(begin.options.user.id),
|
|
551
|
+
},
|
|
552
|
+
excludeCredentials: (begin.options.excludeCredentials || []).map((cred) => ({
|
|
553
|
+
...cred,
|
|
554
|
+
id: base64URLToBuffer(cred.id),
|
|
555
|
+
})),
|
|
556
|
+
};
|
|
557
|
+
const credential = (await navigator.credentials.create({ publicKey: options }));
|
|
558
|
+
if (!credential?.response)
|
|
559
|
+
throw new Error('Passkey registration cancelled or failed');
|
|
560
|
+
const attestationResponse = credential.response;
|
|
561
|
+
const completeRes = await apiFetch(`${config.issuer}/api/v1/passkey/register/complete?request_id=${begin.request_id}`, {
|
|
562
|
+
method: 'POST',
|
|
563
|
+
headers: { 'Content-Type': 'application/json' },
|
|
564
|
+
body: JSON.stringify({
|
|
565
|
+
id: credential.id,
|
|
566
|
+
rawId: bufferToBase64URL(credential.rawId),
|
|
567
|
+
type: credential.type,
|
|
568
|
+
name,
|
|
569
|
+
response: {
|
|
570
|
+
clientDataJSON: bufferToBase64URL(attestationResponse.clientDataJSON),
|
|
571
|
+
attestationObject: bufferToBase64URL(attestationResponse.attestationObject),
|
|
572
|
+
transports: typeof attestationResponse.getTransports === 'function' ? attestationResponse.getTransports() : [],
|
|
573
|
+
},
|
|
574
|
+
}),
|
|
575
|
+
});
|
|
576
|
+
await parseJsonEnvelope(completeRes, 'Failed to verify passkey registration');
|
|
577
|
+
};
|
|
578
|
+
const deletePasskey = async (id) => {
|
|
579
|
+
const response = await apiFetch(`${config.issuer}/api/v1/passkeys/${id}`, { method: 'DELETE' });
|
|
580
|
+
await parseJsonEnvelope(response, 'Failed to remove passkey');
|
|
581
|
+
};
|
|
582
|
+
const listSessions = async () => {
|
|
583
|
+
const response = await apiFetch(`${config.issuer}/api/v1/auth/sessions`);
|
|
584
|
+
const sessions = await parseJsonEnvelope(response, 'Failed to load sessions');
|
|
585
|
+
return sessions.sort((a, b) => new Date(b.last_active_at).getTime() - new Date(a.last_active_at).getTime());
|
|
586
|
+
};
|
|
587
|
+
const getCurrentSession = async () => {
|
|
588
|
+
const response = await apiFetch(`${config.issuer}/api/v1/auth/session`);
|
|
589
|
+
return parseJsonEnvelope(response, 'Failed to load current session');
|
|
590
|
+
};
|
|
591
|
+
const revokeSession = async (id) => {
|
|
592
|
+
const response = await apiFetch(`${config.issuer}/api/v1/auth/sessions/${id}`, { method: 'DELETE' });
|
|
593
|
+
await parseJsonEnvelope(response, 'Failed to revoke session');
|
|
594
|
+
};
|
|
595
|
+
return {
|
|
596
|
+
config,
|
|
597
|
+
storageKeys,
|
|
598
|
+
getAccessToken,
|
|
599
|
+
getRefreshToken,
|
|
600
|
+
getUserProfile: readProfile,
|
|
601
|
+
getSnapshot,
|
|
602
|
+
isLoggedIn: () => Boolean(getAccessToken()),
|
|
603
|
+
isAdmin: () => hasAdminAccess(readProfile(), getAccessToken()),
|
|
604
|
+
saveTokenSet,
|
|
605
|
+
clear,
|
|
606
|
+
logout,
|
|
607
|
+
redirectToAuthorize,
|
|
608
|
+
exchangeCodeForToken,
|
|
609
|
+
handleRedirectCallback,
|
|
610
|
+
fetchUserProfile,
|
|
611
|
+
refreshAccessToken,
|
|
612
|
+
apiFetch,
|
|
613
|
+
loginWithPassword,
|
|
614
|
+
verifyMfa,
|
|
615
|
+
loginWithPasskey,
|
|
616
|
+
updateProfile,
|
|
617
|
+
changePassword,
|
|
618
|
+
requestEmailChange,
|
|
619
|
+
confirmEmailChange,
|
|
620
|
+
getMfaStatus,
|
|
621
|
+
enrollMfa,
|
|
622
|
+
activateMfa,
|
|
623
|
+
disableMfa,
|
|
624
|
+
generateBackupCodes,
|
|
625
|
+
listPasskeys,
|
|
626
|
+
registerPasskey,
|
|
627
|
+
deletePasskey,
|
|
628
|
+
listSessions,
|
|
629
|
+
getCurrentSession,
|
|
630
|
+
revokeSession,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gosso/client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Browser SDK for Gosso OAuth/OIDC SPA clients.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "gosso contributors",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/rushairer/gosso-client.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/rushairer/gosso-client/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/rushairer/gosso-client#readme",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"gosso",
|
|
18
|
+
"oidc",
|
|
19
|
+
"oauth2",
|
|
20
|
+
"pkce",
|
|
21
|
+
"webauthn",
|
|
22
|
+
"passkeys",
|
|
23
|
+
"sso",
|
|
24
|
+
"spa"
|
|
25
|
+
],
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"README.md",
|
|
30
|
+
"CHANGELOG.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"main": "./dist/index.js",
|
|
34
|
+
"module": "./dist/index.js",
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"exports": {
|
|
37
|
+
".": {
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"import": "./dist/index.js"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsc -p tsconfig.build.json",
|
|
44
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"prepack": "npm run build",
|
|
47
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"jsdom": "^29.1.1",
|
|
51
|
+
"typescript": "~6.0.2",
|
|
52
|
+
"vitest": "^4.1.9"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=20"
|
|
56
|
+
}
|
|
57
|
+
}
|