@gosso/client 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 CHANGED
@@ -6,6 +6,49 @@ The format is based on Keep a Changelog, and this project follows Semantic Versi
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.4.0] - 2026-08-24
10
+
11
+ ### Changed
12
+ - Promote `0.4.0-rc.2` to `latest` without runtime code changes after Blog and Admin integration verification.
13
+
14
+ ## [0.4.0-rc.2] - 2026-08-24
15
+
16
+ ### Fixed
17
+ - Correct the release workflow's tag-to-package version validation before its first npm publish attempt.
18
+
19
+ ## [0.4.0-rc.1] - 2026-08-24
20
+
21
+ ### Added
22
+ - Add typed `ApiError` responses with HTTP status and stable error codes.
23
+ - Add SDK-owned password reset request and completion methods.
24
+ - Add exact `allowedApiOrigins` enforcement for authenticated requests.
25
+ - Add a 0.4 migration guide and package provenance release workflow.
26
+
27
+ ### Changed
28
+ - Normalize Gouno, OAuth, empty, and invalid JSON API responses through the shared envelope parser.
29
+ - Make HttpOnly Cookie Session the default when `sessionMode` is omitted.
30
+ - Return a discriminated Cookie or legacy-token result from OAuth callbacks.
31
+ - Keep explicit legacy token sessions in memory only; page reload requires reauthentication.
32
+
33
+ ### Security
34
+ - Fail closed when Web Crypto is unavailable instead of generating OAuth state and PKCE verifier values with `Math.random`.
35
+ - Stop writing access and refresh tokens to Web Storage or JavaScript cookies.
36
+ - Reject credentialed requests to untrusted origins and reject external or protocol-relative login/logout return paths.
37
+
38
+ ## [0.3.0] - 2026-08-15
39
+ ### Added
40
+ - Add `subscribe(listener)` to `GossoClient` so every SPA can observe the
41
+ cookie-session snapshot without maintaining a duplicate authentication store.
42
+ - Add `refreshIdentityRequests` for the GOSSO Admin SPA, whose protected API
43
+ requests are served by the same origin as the identity provider.
44
+
45
+ ## [0.2.1] - 2026-08-14
46
+ ### Fixed
47
+ - Select application and Gosso CSRF cookies by exact request target instead of cookie order.
48
+ - Recover an expired Gosso CSRF cookie before Cookie Session refresh, retry application requests once, and restart PKCE without redirect loops when refresh fails.
49
+ - Coordinate Cookie Session refresh within a page and across tabs using single-flight and Web Locks.
50
+ - Clear client state after logout only when Gosso confirms server-side session revocation.
51
+
9
52
  ## [0.2.0] - 2026-08-12
10
53
  ### Added
11
54
  - Add opt-in `sessionMode: "cookie"` for HttpOnly Cookie-backed SPA sessions. It keeps access and refresh tokens out of JavaScript-accessible storage while preserving PKCE, MFA, passkeys and account-management APIs.
package/MIGRATING.md ADDED
@@ -0,0 +1,15 @@
1
+ # Migrating to 0.4
2
+
3
+ Version 0.4 makes HttpOnly Cookie Session the default. Omit `sessionMode`, or
4
+ set it to `cookie`. Gosso must be reachable over HTTPS and configured with an
5
+ exact credentialed CORS origin when it is not same-origin.
6
+
7
+ Cookie callbacks return `{ sessionMode: 'cookie', redirectTo }`; tokens are set
8
+ only by Gosso with `Set-Cookie` and are not available to JavaScript. Replace
9
+ code that reads `tokenSet`, Web Storage, or an access-token cookie with
10
+ `getSnapshot()`, `subscribe()`, and `apiFetch()`.
11
+
12
+ `sessionMode: 'token'` remains an explicit legacy option. Its tokens live only
13
+ in memory and disappear on reload. `apiFetch` sends credentials only to the
14
+ page origin, issuer origin, or an exact origin listed in `allowedApiOrigins`.
15
+ All login and logout return values must be application-local paths.
package/README.md CHANGED
@@ -5,10 +5,12 @@ Browser SDK for Gosso OAuth/OIDC single-page application clients.
5
5
  `@gosso/client` provides the protocol and account self-service layer for ordinary Gosso clients:
6
6
 
7
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
8
+ - HttpOnly Cookie Session, userinfo loading, logout, and automatic refresh
9
+ - origin-restricted authenticated `apiFetch` with CSRF handling and 401 retry
10
10
  - username/password login, MFA verification, and passkey login
11
11
  - profile, password, email, MFA, passkey, and session management APIs
12
+ - password reset request and completion APIs
13
+ - typed API errors for consistent consumer handling
12
14
 
13
15
  The package intentionally does not ship React UI. Build app-specific pages with your own design system and call the SDK methods underneath.
14
16
 
@@ -31,6 +33,8 @@ export const gossoClient = createGossoClient({
31
33
  postLoginDefaultPath: '/admin',
32
34
  loginPath: '/login',
33
35
  storagePrefix: 'my-app',
36
+ sessionProfileEndpoint: '/api/me/session',
37
+ csrfCookieName: 'blog_csrf_token',
34
38
  });
35
39
  ```
36
40
 
@@ -87,6 +91,9 @@ await gossoClient.confirmEmailChange(newEmail, code);
87
91
  const mfaStatus = await gossoClient.getMfaStatus();
88
92
  const passkeys = await gossoClient.listPasskeys();
89
93
  const sessions = await gossoClient.listSessions();
94
+
95
+ await gossoClient.requestPasswordReset(email);
96
+ await gossoClient.resetPassword(resetToken, newPassword);
90
97
  ```
91
98
 
92
99
  ## Configuration
@@ -100,20 +107,38 @@ interface GossoClientConfig {
100
107
  postLoginDefaultPath: string;
101
108
  loginPath: string;
102
109
  storagePrefix: string;
110
+ sessionMode?: 'token' | 'cookie';
111
+ allowedApiOrigins?: readonly string[];
112
+ sessionProfileEndpoint?: string;
113
+ csrfCookieName?: string;
103
114
  fetchImpl?: typeof fetch;
104
115
  onAuthRequired?: () => void;
105
116
  onSessionChanged?: (snapshot: SessionSnapshot) => void;
106
117
  }
107
118
  ```
108
119
 
109
- Use a unique `storagePrefix` for each SPA on the same origin to avoid token collisions.
120
+ Cookie Session is the default when `sessionMode` is omitted. Use a unique
121
+ `storagePrefix` for each SPA on the same origin to isolate transient PKCE,
122
+ profile, and refresh-coordination state.
123
+
124
+ `apiFetch` accepts the page origin and issuer origin by default. Add only exact,
125
+ trusted HTTPS origins to `allowedApiOrigins`; credentials are rejected before a
126
+ request is sent to any other origin.
127
+
128
+ In Cookie Session mode, `csrfCookieName` belongs to the application API only. Gosso identity requests always use `__Host-csrf_token` on HTTPS, or `csrf_token` only for an HTTP development issuer. Cookie lookup is exact and never depends on `document.cookie` order.
129
+
130
+ Gosso's default lifetimes are independent: Access Token 15 minutes, Refresh Token 168 hours, Session 24 hours, and CSRF Cookie 4 hours (capped at 24 hours). A missing CSRF Cookie does not invalidate the Refresh Token: the SDK first performs a safe session GET to recover Gosso's CSRF Cookie, then refreshes and retries the original application request once.
110
131
 
111
132
  ## Security Notes
112
133
 
113
134
  - Use Authorization Code + PKCE for browser clients.
114
135
  - Serve production clients over HTTPS.
115
136
  - 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.
137
+ - Cookie Session is the secure default; access and refresh tokens remain in server-set `__Host-*` HttpOnly cookies and are never written by the SDK to Web Storage or JavaScript cookies.
138
+ - Explicit `sessionMode: "token"` is a legacy, tab-local mode. Tokens stay in memory, disappear on reload, and are sent only to configured origins.
139
+ - Cookie Session refresh is single-flight within a page and coordinated across tabs with the Web Locks API. Only a non-sensitive refresh generation marker is stored in `localStorage`.
140
+ - OAuth state and PKCE verifier generation requires Web Crypto and fails closed when a cryptographically secure random source is unavailable.
141
+ - Login and logout return locations must be application-local paths. See [MIGRATING.md](./MIGRATING.md) before upgrading from 0.3 or earlier.
117
142
 
118
143
  ## License
119
144
 
package/SECURITY.md ADDED
@@ -0,0 +1,6 @@
1
+ # Security Policy
2
+
3
+ Only the latest minor version receives security fixes. Report vulnerabilities
4
+ through GitHub Security Advisories and do not disclose credentials or exploit
5
+ details in public issues. Include the affected version, impact, and a minimal
6
+ reproduction. Maintainers aim to acknowledge reports within seven days.
@@ -0,0 +1,76 @@
1
+ import type { AuthCallbackResult, AuthenticationResult, GossoClientConfig, LoginResult, MfaEnrollment, MfaStatus, PasskeyInfo, SessionInfo, SessionListener, SessionSnapshot, TokenResponse, UserProfile } from "./types.js";
2
+ export declare const defaultConfig: Pick<GossoClientConfig, "scope" | "postLoginDefaultPath" | "loginPath" | "storagePrefix" | "sessionMode">;
3
+ export declare function createGossoClient(inputConfig: GossoClientConfig): {
4
+ config: {
5
+ issuer: string;
6
+ sessionMode: "token" | "cookie" | undefined;
7
+ clientId: string;
8
+ redirectUri: string;
9
+ scope: string;
10
+ postLoginDefaultPath: string;
11
+ loginPath: string;
12
+ storagePrefix: string;
13
+ allowedApiOrigins?: readonly string[];
14
+ sessionProfileEndpoint?: string;
15
+ csrfCookieName?: string;
16
+ refreshIdentityRequests?: boolean;
17
+ fetchImpl?: typeof fetch;
18
+ onAuthRequired?: () => void;
19
+ onSessionChanged?: (snapshot: SessionSnapshot) => void;
20
+ };
21
+ storageKeys: {
22
+ accessToken: string;
23
+ refreshToken: string;
24
+ userProfile: string;
25
+ pkceVerifier: string;
26
+ authState: string;
27
+ postLoginRedirect: string;
28
+ tokenIssuedAt: string;
29
+ tokenExpiresIn: string;
30
+ refreshLock: string;
31
+ refreshGeneration: string;
32
+ authRedirectGuard: string;
33
+ };
34
+ getAccessToken: () => string | null;
35
+ getRefreshToken: () => string | null;
36
+ getUserProfile: () => UserProfile | null;
37
+ getSnapshot: () => SessionSnapshot;
38
+ subscribe: (listener: SessionListener) => () => void;
39
+ isLoggedIn: () => boolean;
40
+ isAdmin: () => boolean;
41
+ saveTokenSet: (data: TokenResponse | {
42
+ access_token: string;
43
+ refresh_token?: string;
44
+ expires_in?: number;
45
+ }) => void;
46
+ clear: () => void;
47
+ logout: (redirectTo?: string) => Promise<void>;
48
+ redirectToAuthorize: (customRedirectUri?: string) => Promise<void>;
49
+ exchangeCodeForToken: (code: string, state: string) => Promise<AuthenticationResult>;
50
+ handleRedirectCallback: (code: string, state: string) => Promise<AuthCallbackResult>;
51
+ fetchUserProfile: (accessToken?: string | null) => Promise<UserProfile>;
52
+ refreshAccessToken: () => Promise<string>;
53
+ apiFetch: (url: string, options?: RequestInit) => Promise<Response>;
54
+ loginWithPassword: (username: string, password: string) => Promise<LoginResult>;
55
+ requestPasswordReset: (email: string) => Promise<void>;
56
+ resetPassword: (token: string, newPassword: string) => Promise<void>;
57
+ verifyMfa: (mfaToken: string, code: string, type?: "totp" | "passkey") => Promise<AuthenticationResult>;
58
+ loginWithPasskey: () => Promise<AuthenticationResult>;
59
+ updateProfile: (displayName: string) => Promise<UserProfile>;
60
+ changePassword: (currentPassword: string, newPassword: string) => Promise<void>;
61
+ requestEmailChange: (newEmail: string, password: string) => Promise<void>;
62
+ confirmEmailChange: (newEmail: string, code: string) => Promise<UserProfile>;
63
+ getMfaStatus: () => Promise<MfaStatus>;
64
+ enrollMfa: () => Promise<MfaEnrollment>;
65
+ activateMfa: (code: string) => Promise<string[]>;
66
+ disableMfa: (currentPassword: string) => Promise<void>;
67
+ generateBackupCodes: () => Promise<string[]>;
68
+ listPasskeys: () => Promise<PasskeyInfo[]>;
69
+ registerPasskey: (name: string) => Promise<void>;
70
+ deletePasskey: (id: string) => Promise<void>;
71
+ listSessions: () => Promise<SessionInfo[]>;
72
+ getCurrentSession: () => Promise<SessionInfo>;
73
+ revokeSession: (id: string) => Promise<void>;
74
+ };
75
+ export type GossoClient = ReturnType<typeof createGossoClient>;
76
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,EACjB,WAAW,EACX,aAAa,EACb,SAAS,EAET,WAAW,EAEX,WAAW,EACX,eAAe,EACf,eAAe,EACf,aAAa,EACb,WAAW,EACZ,MAAM,YAAY,CAAC;AA+BpB,eAAO,MAAM,aAAa,EAAE,IAAI,CAC9B,iBAAiB,EACf,OAAO,GACP,sBAAsB,GACtB,WAAW,GACX,eAAe,GACf,aAAa,CAOhB,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0BA8CnC,MAAM,GAAG,IAAI;2BAEZ,MAAM,GAAG,IAAI;0BAZjB,WAAW,GAAG,IAAI;uBAelB,eAAe;0BAwBV,eAAe;;;yBAStC,aAAa,GACb;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;;;8CAqapB,MAAM;iCAwBrD,MAAM,SACL,MAAM,KACZ,OAAO,CAAC,oBAAoB,CAAC;mCA0CxB,MAAM,SACL,MAAM,KACZ,OAAO,CAAC,kBAAkB,CAAC;uDArO3B,OAAO,CAAC,WAAW,CAAC;8BA9Dc,OAAO,CAAC,MAAM,CAAC;oBAkH7C,MAAM,YACF,WAAW,KACnB,OAAO,CAAC,QAAQ,CAAC;kCAqOR,MAAM,YACN,MAAM,KACf,OAAO,CAAC,WAAW,CAAC;kCAuBoB,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;2BAiBxD,MAAM,eACA,MAAM,KAClB,OAAO,CAAC,IAAI,CAAC;0BAcJ,MAAM,QACV,MAAM,SACN,MAAM,GAAG,SAAS,KACvB,OAAO,CAAC,oBAAoB,CAAC;4BAqBG,OAAO,CAAC,oBAAoB,CAAC;iCAuEtB,MAAM;sCAW7B,MAAM,eACV,MAAM;mCAgBuB,MAAM,YAAY,MAAM;mCAexB,MAAM,QAAQ,MAAM;wBAgBjC,OAAO,CAAC,SAAS,CAAC;qBAKrB,OAAO,CAAC,aAAa,CAAC;wBAOjB,MAAM,KAAG,OAAO,CAAC,MAAM,EAAE,CAAC;kCAqBhB,MAAM;+BASX,OAAO,CAAC,MAAM,EAAE,CAAC;wBAYxB,OAAO,CAAC,WAAW,EAAE,CAAC;4BAQhB,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;wBAkE1B,MAAM;wBAOR,OAAO,CAAC,WAAW,EAAE,CAAC;6BAajB,OAAO,CAAC,WAAW,CAAC;wBAQvB,MAAM;EAiDxC;AAED,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC"}