@pofsis-nbt/pofsis-auth 1.0.3 → 1.2.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.
Files changed (50) hide show
  1. package/Documentation.md +143 -141
  2. package/README.md +130 -119
  3. package/dist/PofsisAuth.d.ts +1 -5
  4. package/dist/PofsisAuth.d.ts.map +1 -1
  5. package/dist/PofsisAuthClient-CRYYsJYc.cjs +2 -0
  6. package/dist/PofsisAuthClient-CRYYsJYc.cjs.map +1 -0
  7. package/dist/PofsisAuthClient-c9BY8iNp.js +438 -0
  8. package/dist/PofsisAuthClient-c9BY8iNp.js.map +1 -0
  9. package/dist/PofsisAuthModal-DGbtRZ4f.js +495 -0
  10. package/dist/PofsisAuthModal-DGbtRZ4f.js.map +1 -0
  11. package/dist/PofsisAuthModal-DIzzLJSP.cjs +2 -0
  12. package/dist/PofsisAuthModal-DIzzLJSP.cjs.map +1 -0
  13. package/dist/auth/config.d.ts +3 -5
  14. package/dist/auth/config.d.ts.map +1 -1
  15. package/dist/components/AuthPathShell.d.ts +2 -1
  16. package/dist/components/AuthPathShell.d.ts.map +1 -1
  17. package/dist/components/PofsisAuthModal.d.ts +3 -0
  18. package/dist/components/PofsisAuthModal.d.ts.map +1 -0
  19. package/dist/components/index.d.ts +1 -0
  20. package/dist/components/index.d.ts.map +1 -1
  21. package/dist/core/PofsisAuthClient.d.ts +47 -0
  22. package/dist/core/PofsisAuthClient.d.ts.map +1 -0
  23. package/dist/core/index.d.ts +4 -0
  24. package/dist/core/index.d.ts.map +1 -0
  25. package/dist/core/popupProtocol.d.ts +11 -0
  26. package/dist/core/popupProtocol.d.ts.map +1 -0
  27. package/dist/core.cjs +1 -0
  28. package/dist/core.js +2 -0
  29. package/dist/elements/index.d.ts +12 -0
  30. package/dist/elements/index.d.ts.map +1 -0
  31. package/dist/elements.cjs +2 -0
  32. package/dist/elements.cjs.map +1 -0
  33. package/dist/elements.js +75 -0
  34. package/dist/elements.js.map +1 -0
  35. package/dist/index.cjs +1 -2
  36. package/dist/index.d.ts +3 -2
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +4 -623
  39. package/dist/pofsis-auth-elements.js +2 -0
  40. package/dist/pofsis-auth-elements.js.map +1 -0
  41. package/dist/react/index.d.ts +7 -0
  42. package/dist/react/index.d.ts.map +1 -0
  43. package/dist/react.cjs +1 -0
  44. package/dist/react.js +2 -0
  45. package/dist/types.d.ts +51 -1
  46. package/dist/types.d.ts.map +1 -1
  47. package/docs/popup-integration.md +178 -0
  48. package/package.json +87 -62
  49. package/dist/index.cjs.map +0 -1
  50. package/dist/index.js.map +0 -1
package/Documentation.md CHANGED
@@ -1,141 +1,143 @@
1
- # POFSIS Auth Documentation
2
-
3
- This is the complete public API and integration reference for `@pofsis-nbt/pofsis-auth@1.0.1`.
4
-
5
- The repository also includes an interactive [POFSIS Auth documentation portal](./index.html) with required environment configuration, generated setup examples, and authentication scenario links. The portal does not persist or transmit entered values and is intentionally excluded from the npm package.
6
-
7
- ## Integration process
8
-
9
- 1. Install `@pofsis-nbt/pofsis-auth` with its React, React DOM, and optional Axios peers.
10
- 2. Import `@pofsis-nbt/pofsis-auth/style.css` once.
11
- 3. Wrap the application in `PofsisAuth` and supply runtime configuration.
12
- 4. Protect authenticated content with `RequireAuth` or use `useAuth` directly.
13
- 5. Register `/auth/callback` and the application origin with the POFSIS Auth service.
14
-
15
- The consuming application owns environment lookup. The library never reads `import.meta.env`.
16
-
17
- ## Public components
18
-
19
- ### `PofsisAuth`
20
-
21
- The provider restores the cookie-backed session, manages the in-memory access token, coordinates cross-tab logout, runs OAuth/PKCE, and handles fixed auth paths.
22
-
23
- `PofsisAuthProps`:
24
-
25
- | Prop | Type | Required | Meaning |
26
- | --------------- | ------------------------ | -------- | -------------------------------------- |
27
- | `children` | `ReactNode` | yes | Application content |
28
- | `appName` | `string` | yes | Registered POFSIS application identity |
29
- | `apiBaseUrl` | `string` | yes | Auth API base URL |
30
- | `authUrl` | `string` | yes | OAuth authorize URL |
31
- | `scope` | `string` | no | Defaults to `openid profile email` |
32
- | `postLoginPath` | `string` | no | Callback destination; defaults to `/` |
33
- | `components` | `AuthComponentOverrides` | no | Custom loading and error screens |
34
-
35
- ```tsx
36
- <PofsisAuth
37
- appName="ORBIT"
38
- apiBaseUrl={runtimeConfig.authApi}
39
- authUrl={runtimeConfig.authorizeUrl}
40
- components={{ LoadingScreen: AppLoader, ErrorScreen: AppAuthError }}
41
- >
42
- <App />
43
- </PofsisAuth>
44
- ```
45
-
46
- ### `RequireAuth`
47
-
48
- `RequireAuth({ children, fallback?, onUnauthenticated? })` renders authenticated content. `fallback` overrides loading UI for that guard. Without `onUnauthenticated`, an unauthenticated visitor starts login; failures route to `/auth/error`.
49
-
50
- ### `AuthLoadingScreen`
51
-
52
- The default full-screen loader. `AuthLoadingScreenProps.callback` changes the message for login/callback work.
53
-
54
- ### `AuthErrorScreen`
55
-
56
- The default error page. `AuthErrorScreenProps.retryHref` defaults to `/login`. Its illustration is emitted into the compiled bundle as a data asset.
57
-
58
- ## `useAuth`
59
-
60
- `useAuth(): AuthContextValue` must be called below `PofsisAuth`.
61
-
62
- | Value or method | Meaning |
63
- | --------------------------------------- | ----------------------------------------------------------------------------- |
64
- | `user` | Current `PofsisUser`, or `null` |
65
- | `session` | Current `SessionPayload`, or `null` |
66
- | `isAuthenticated` | Whether a session is present |
67
- | `isLoading` | Whether initial session restoration is running |
68
- | `login(options?)` | Starts OAuth/PKCE login |
69
- | `logout()` | Ends the remote session, clears local state, broadcasts logout, and redirects |
70
- | `refreshSession()` | Exchanges the refresh cookie for a new access token |
71
- | `getAccessToken()` | Returns the in-memory token or `null` |
72
- | `getAccountProfile()` | Fetches and merges the current `AccountProfile` |
73
- | `updateAccountProfile(profile)` | Updates first and/or last name |
74
- | `uploadAccountProfileImage(kind, file)` | Uploads a `profile` or `cover` image |
75
- | `deleteAccountProfileImage(kind)` | Removes a `profile` or `cover` image |
76
- | `completeCallback(code, state)` | Completes PKCE callback; normally used internally |
77
-
78
- ## Login options and URL hints
79
-
80
- `login(options)` accepts `AuthLaunchOptions`:
81
-
82
- - `returnUrl`: accepted only when it starts with `/` and not `//`; unsafe or external values are discarded.
83
- - `screen`: `login`, `signup`, or `reset`; legacy `register` is normalized to `signup`.
84
- - `loginHint`: valid email or Philippine mobile number. Emails are trimmed/lowercased; mobile numbers normalize to `+63`; invalid or values over 320 characters are ignored.
85
- - `method`: `password`, `passkey`, or `otp`. Login accepts all three methods. Signup accepts `password` or `otp`; OTP signup continues to the verification-code page after the identifier is submitted.
86
-
87
- The `/login` route also reads `screen`, `login_hint`, and `method`. Legacy `email` is accepted only as an email hint and only when canonical `login_hint` is absent. Unknown values are ignored. Signup defaults to password when no method is supplied and preserves an explicit `otp`; reset does not forward a method.
88
-
89
- Fixed paths are `/login`, `/auth/callback`, and `/auth/error`. Saved callback destinations are restricted to safe local paths.
90
-
91
- ## Axios interceptor
92
-
93
- `attachAuthInterceptor(client, getToken, refresh)` adds the current bearer token and performs one refresh-and-retry after a 401. It returns a cleanup function that ejects both interceptors. Axios is optional unless this helper is used.
94
-
95
- ## Configuration helper
96
-
97
- `resolveConfig(overrides: ConfigOverrides)` validates `appName`, `apiBaseUrl`, and `authUrl`, trims values, removes a trailing slash from URLs, and supplies the default scope. Missing required values throw synchronously.
98
-
99
- ## Exported types
100
-
101
- - `PofsisAuthProps`, `PofsisAuthConfig`, `ConfigOverrides`, `AuthComponentOverrides`
102
- - `AuthLoadingScreenProps`, `AuthErrorScreenProps`, `RequireAuthProps`
103
- - `AuthContextValue`, `AuthLaunchOptions`, `AuthScreen`, `AuthMethod`
104
- - `PofsisUser`, `AccountProfile`, `SessionPayload`, `TokenResponse`
105
- - `OAuthClientConfig`
106
-
107
- `SessionPayload` contains `token_type`, `expires_in`, and `user`. `TokenResponse` adds `access_token`. Access tokens remain in memory. `PofsisUser` exposes identity, display/profile media, and optional roles. `AccountProfile` represents the editable account profile and timestamps. `OAuthClientConfig` represents server-resolved client, redirect, and logout details.
108
-
109
- ## Module formats
110
-
111
- ESM/JavaScript and TypeScript use the same package import:
112
-
113
- ```ts
114
- import { PofsisAuth, useAuth } from '@pofsis-nbt/pofsis-auth'
115
- ```
116
-
117
- CommonJS uses:
118
-
119
- ```js
120
- const { PofsisAuth, useAuth } = require('@pofsis-nbt/pofsis-auth')
121
- ```
122
-
123
- TypeScript declarations resolve from `dist/index.d.ts`. No separate TypeScript package is needed.
124
-
125
- ## Build and verification process
126
-
127
- ```bash
128
- npm run format:check
129
- npm run typecheck
130
- npm test
131
- npm run build
132
- npm run pack:check
133
- ```
134
-
135
- `dist/` contains ESM and CommonJS JavaScript, source maps, declaration files/maps, component declarations, and `style.css`. The npm package includes only `dist`, `package.json`, `README.md`, and `Documentation.md`.
136
-
137
- ## Public boundary and migration
138
-
139
- Supported imports are the package root and `@pofsis-nbt/pofsis-auth/style.css`. Files under `auth/`, `components/`, `context/`, `src/`, and `dist/` are implementation details and are not exported.
140
-
141
- Migrating from `@pofsis/auth-react` requires changing the package name, passing explicit provider configuration, and importing the stylesheet. OAuth paths, PKCE/session behavior, hooks, profile methods, UI overrides, and interceptor behavior remain available.
1
+ # POFSIS Auth Documentation
2
+
3
+ This is the complete public API and integration reference for `@pofsis-nbt/pofsis-auth@1.2.0`.
4
+
5
+ The repository also includes an interactive [POFSIS Auth documentation portal](./index.html) with required environment configuration, generated setup examples, and authentication scenario links. The portal does not persist or transmit entered values and is intentionally excluded from the npm package.
6
+
7
+ The [popup integration guide](./docs/popup-integration.md) is the canonical consumer reference for framework-neutral, React, and Web Component popup launches.
8
+
9
+ ## Integration process
10
+
11
+ 1. Install `@pofsis-nbt/pofsis-auth` with its React, React DOM, and optional Axios peers.
12
+ 2. Import `@pofsis-nbt/pofsis-auth/style.css` once.
13
+ 3. Wrap the application in `PofsisAuth` and supply runtime configuration.
14
+ 4. Protect authenticated content with `RequireAuth` or use `useAuth` directly.
15
+ 5. Register `/auth/callback` and the application origin with the POFSIS Auth service.
16
+
17
+ The consuming application owns environment lookup. The library never reads `import.meta.env`.
18
+
19
+ ## Public components
20
+
21
+ ### `PofsisAuth`
22
+
23
+ The provider restores the cookie-backed session, manages the in-memory access token, coordinates cross-tab logout, runs OAuth/PKCE, and handles fixed auth paths.
24
+
25
+ `PofsisAuthProps`:
26
+
27
+ | Prop | Type | Required | Meaning |
28
+ | --------------- | ------------------------ | -------- | -------------------------------------- |
29
+ | `children` | `ReactNode` | yes | Application content |
30
+ | `appName` | `string` | yes | Registered POFSIS application identity |
31
+ | `apiBaseUrl` | `string` | yes | Auth API base URL |
32
+ | `authUrl` | `string` | yes | OAuth authorize URL |
33
+ | `scope` | `string` | no | Defaults to `openid profile email` |
34
+ | `postLoginPath` | `string` | no | Callback destination; defaults to `/` |
35
+ | `components` | `AuthComponentOverrides` | no | Custom loading and error screens |
36
+
37
+ ```tsx
38
+ <PofsisAuth
39
+ appName="ORBIT"
40
+ apiBaseUrl={runtimeConfig.authApi}
41
+ authUrl={runtimeConfig.authorizeUrl}
42
+ components={{ LoadingScreen: AppLoader, ErrorScreen: AppAuthError }}
43
+ >
44
+ <App />
45
+ </PofsisAuth>
46
+ ```
47
+
48
+ ### `RequireAuth`
49
+
50
+ `RequireAuth({ children, fallback?, onUnauthenticated? })` renders authenticated content. `fallback` overrides loading UI for that guard. Without `onUnauthenticated`, an unauthenticated visitor starts login; failures route to `/auth/error`.
51
+
52
+ ### `AuthLoadingScreen`
53
+
54
+ The default full-screen loader. `AuthLoadingScreenProps.callback` changes the message for login/callback work.
55
+
56
+ ### `AuthErrorScreen`
57
+
58
+ The default error page. `AuthErrorScreenProps.retryHref` defaults to `/login`. Its illustration is emitted into the compiled bundle as a data asset.
59
+
60
+ ## `useAuth`
61
+
62
+ `useAuth(): AuthContextValue` must be called below `PofsisAuth`.
63
+
64
+ | Value or method | Meaning |
65
+ | --------------------------------------- | ----------------------------------------------------------------------------- |
66
+ | `user` | Current `PofsisUser`, or `null` |
67
+ | `session` | Current `SessionPayload`, or `null` |
68
+ | `isAuthenticated` | Whether a session is present |
69
+ | `isLoading` | Whether initial session restoration is running |
70
+ | `login(options?)` | Starts OAuth/PKCE login |
71
+ | `logout()` | Ends the remote session, clears local state, broadcasts logout, and redirects |
72
+ | `refreshSession()` | Exchanges the refresh cookie for a new access token |
73
+ | `getAccessToken()` | Returns the in-memory token or `null` |
74
+ | `getAccountProfile()` | Fetches and merges the current `AccountProfile` |
75
+ | `updateAccountProfile(profile)` | Updates first and/or last name |
76
+ | `uploadAccountProfileImage(kind, file)` | Uploads a `profile` or `cover` image |
77
+ | `deleteAccountProfileImage(kind)` | Removes a `profile` or `cover` image |
78
+ | `completeCallback(code, state)` | Completes PKCE callback; normally used internally |
79
+
80
+ ## Login options and URL hints
81
+
82
+ `login(options)` accepts `AuthLaunchOptions`:
83
+
84
+ - `returnUrl`: accepted only when it starts with `/` and not `//`; unsafe or external values are discarded.
85
+ - `screen`: `login`, `signup`, or `reset`; legacy `register` is normalized to `signup`.
86
+ - `loginHint`: valid email or Philippine mobile number. Emails are trimmed/lowercased; mobile numbers normalize to `+63`; invalid or values over 320 characters are ignored.
87
+ - `method`: `password`, `passkey`, or `otp`. Login accepts all three methods. Signup accepts `password` or `otp`; OTP signup continues to the verification-code page after the identifier is submitted.
88
+
89
+ The `/login` route also reads `screen`, `login_hint`, and `method`. Legacy `email` is accepted only as an email hint and only when canonical `login_hint` is absent. Unknown values are ignored. Signup defaults to password when no method is supplied and preserves an explicit `otp`; reset does not forward a method.
90
+
91
+ Fixed paths are `/login`, `/auth/callback`, and `/auth/error`. Saved callback destinations are restricted to safe local paths.
92
+
93
+ ## Axios interceptor
94
+
95
+ `attachAuthInterceptor(client, getToken, refresh)` adds the current bearer token and performs one refresh-and-retry after a 401. It returns a cleanup function that ejects both interceptors. Axios is optional unless this helper is used.
96
+
97
+ ## Configuration helper
98
+
99
+ `resolveConfig(overrides: ConfigOverrides)` validates `appName`, `apiBaseUrl`, and `authUrl`, trims values, removes a trailing slash from URLs, and supplies the default scope. Missing required values throw synchronously.
100
+
101
+ ## Exported types
102
+
103
+ - `PofsisAuthProps`, `PofsisAuthConfig`, `ConfigOverrides`, `AuthComponentOverrides`
104
+ - `AuthLoadingScreenProps`, `AuthErrorScreenProps`, `RequireAuthProps`
105
+ - `AuthContextValue`, `AuthLaunchOptions`, `AuthScreen`, `AuthMethod`
106
+ - `PofsisUser`, `AccountProfile`, `SessionPayload`, `TokenResponse`
107
+ - `OAuthClientConfig`
108
+
109
+ `SessionPayload` contains `token_type`, `expires_in`, and `user`. `TokenResponse` adds `access_token`. Access tokens remain in memory. `PofsisUser` exposes identity, display/profile media, and optional roles. `AccountProfile` represents the editable account profile and timestamps. `OAuthClientConfig` represents server-resolved client, redirect, and logout details.
110
+
111
+ ## Module formats
112
+
113
+ ESM/JavaScript and TypeScript use the same package import:
114
+
115
+ ```ts
116
+ import { PofsisAuth, useAuth } from '@pofsis-nbt/pofsis-auth'
117
+ ```
118
+
119
+ CommonJS uses:
120
+
121
+ ```js
122
+ const { PofsisAuth, useAuth } = require('@pofsis-nbt/pofsis-auth')
123
+ ```
124
+
125
+ TypeScript declarations resolve from `dist/index.d.ts`. No separate TypeScript package is needed.
126
+
127
+ ## Build and verification process
128
+
129
+ ```bash
130
+ npm run format:check
131
+ npm run typecheck
132
+ npm test
133
+ npm run build
134
+ npm run pack:check
135
+ ```
136
+
137
+ `dist/` contains ESM and CommonJS JavaScript, source maps, declaration files/maps, component declarations, and `style.css`. The npm package includes only `dist`, `package.json`, `README.md`, and `Documentation.md`.
138
+
139
+ ## Public boundary and migration
140
+
141
+ Supported imports are the package root and `@pofsis-nbt/pofsis-auth/style.css`. Files under `auth/`, `components/`, `context/`, `src/`, and `dist/` are implementation details and are not exported.
142
+
143
+ Migrating from `@pofsis/auth-react` requires changing the package name, passing explicit provider configuration, and importing the stylesheet. OAuth paths, PKCE/session behavior, hooks, profile methods, UI overrides, and interceptor behavior remain available.
package/README.md CHANGED
@@ -1,119 +1,130 @@
1
- # @pofsis-nbt/pofsis-auth
2
-
3
- React OAuth 2.0/PKCE authentication for POFSIS applications. One package serves JavaScript and TypeScript consumers through ESM, CommonJS, generated declarations, and extracted CSS.
4
-
5
- For guided setup and authentication scenarios, open the repository's interactive [POFSIS Auth documentation portal](./index.html). It validates consumer-supplied environment values and generates safe `/login` launch links without storing them.
6
-
7
- ## Install
8
-
9
- ```bash
10
- npm install @pofsis-nbt/pofsis-auth axios react react-dom
11
- ```
12
-
13
- Import the package stylesheet once in the application entry point:
14
-
15
- ```ts
16
- import '@pofsis-nbt/pofsis-auth/style.css'
17
- ```
18
-
19
- ## Quick start
20
-
21
- ```tsx
22
- import { PofsisAuth, RequireAuth } from '@pofsis-nbt/pofsis-auth'
23
- import '@pofsis-nbt/pofsis-auth/style.css'
24
-
25
- export function App() {
26
- return (
27
- <PofsisAuth
28
- appName="ORBIT"
29
- apiBaseUrl="https://auth-api.example.com"
30
- authUrl="https://auth.example.com/authorize"
31
- >
32
- <RequireAuth>
33
- <Dashboard />
34
- </RequireAuth>
35
- </PofsisAuth>
36
- )
37
- }
38
- ```
39
-
40
- `appName`, `apiBaseUrl`, and `authUrl` are required. The library does not read environment variables. A Vite application may pass its own values:
41
-
42
- ```tsx
43
- <PofsisAuth
44
- appName={import.meta.env.VITE_POFSIS_APP_NAME}
45
- apiBaseUrl={import.meta.env.VITE_POFSIS_API_BASE_URL}
46
- authUrl={import.meta.env.VITE_POFSIS_AUTH_URL}
47
- scope={import.meta.env.VITE_POFSIS_SCOPE}
48
- >
49
- <App />
50
- </PofsisAuth>
51
- ```
52
-
53
- The same props may come from server-injected runtime configuration or hardcoded deployment configuration. The default scope is `openid profile email`.
54
-
55
- ## Session and login
56
-
57
- ```tsx
58
- import { useAuth } from '@pofsis-nbt/pofsis-auth'
59
-
60
- function AccountMenu() {
61
- const { user, login, logout } = useAuth()
62
- if (!user) return <button onClick={() => void login()}>Sign in</button>
63
- return <button onClick={() => void logout()}>Sign out {user.displayName}</button>
64
- }
65
- ```
66
-
67
- The provider handles `/login`, `/auth/callback`, and `/auth/error`. Login supports `screen`, `loginHint`, `method`, and a safe application-relative `returnUrl`. An OTP signup opens the signup page and continues to account verification after the identifier is submitted. See [Documentation.md](./Documentation.md) for the complete API.
68
-
69
- ## Axios
70
-
71
- ```ts
72
- import axios from 'axios'
73
- import { attachAuthInterceptor, useAuth } from '@pofsis-nbt/pofsis-auth'
74
-
75
- const api = axios.create({ baseURL: '/api' })
76
-
77
- function ApiAuthBridge() {
78
- const { getAccessToken, refreshSession } = useAuth()
79
- React.useEffect(
80
- () => attachAuthInterceptor(api, getAccessToken, refreshSession),
81
- [getAccessToken, refreshSession],
82
- )
83
- return null
84
- }
85
- ```
86
-
87
- ## CommonJS
88
-
89
- ```js
90
- const { PofsisAuth, RequireAuth, useAuth } = require('@pofsis-nbt/pofsis-auth')
91
- require('@pofsis-nbt/pofsis-auth/style.css')
92
- ```
93
-
94
- JavaScript consumers use the compiled JavaScript. TypeScript consumers use the same imports and automatically receive declarations.
95
-
96
- ## Development
97
-
98
- Requires Node `^20.19.0` or `>=22.12.0`.
99
-
100
- ```bash
101
- npm install
102
- npm run format:check
103
- npm run typecheck
104
- npm test
105
- npm run build
106
- npm run pack:check
107
- ```
108
-
109
- Build output is written to `dist/` with ESM (`index.js`), CommonJS (`index.cjs`), source maps, declarations, and `style.css`.
110
-
111
- ## Migration
112
-
113
- When migrating from `@pofsis/auth-react`:
114
-
115
- 1. Change imports to `@pofsis-nbt/pofsis-auth`.
116
- 2. Pass `appName`, `apiBaseUrl`, and `authUrl` to `PofsisAuth`.
117
- 3. Import `@pofsis-nbt/pofsis-auth/style.css` once.
118
-
119
- Do not import `src/`, `dist/` internals, or internal auth helpers. Only the package-root exports and `style.css` are supported.
1
+ # @pofsis-nbt/pofsis-auth
2
+
3
+ React OAuth 2.0/PKCE authentication for POFSIS applications. One package serves JavaScript and TypeScript consumers through ESM, CommonJS, generated declarations, and extracted CSS.
4
+
5
+ For guided setup and authentication scenarios, open the repository's interactive [POFSIS Auth documentation portal](./index.html). It validates consumer-supplied environment values and generates safe `/login` launch links without storing them.
6
+
7
+ For hosted popup setup, parameters, callback handling, React usage, and the optional Web Component, see the [popup integration guide](./docs/popup-integration.md).
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @pofsis-nbt/pofsis-auth axios react react-dom
13
+ ```
14
+
15
+ Import the package stylesheet once in the application entry point:
16
+
17
+ ```ts
18
+ import '@pofsis-nbt/pofsis-auth/style.css'
19
+ ```
20
+
21
+ ## Quick start
22
+
23
+ ```tsx
24
+ import { PofsisAuth, RequireAuth } from '@pofsis-nbt/pofsis-auth'
25
+ import '@pofsis-nbt/pofsis-auth/style.css'
26
+
27
+ export function App() {
28
+ return (
29
+ <PofsisAuth
30
+ appName="ORBIT"
31
+ apiBaseUrl="https://auth-api.example.com"
32
+ authUrl="https://auth.example.com/authorize"
33
+ >
34
+ <RequireAuth>
35
+ <Dashboard />
36
+ </RequireAuth>
37
+ </PofsisAuth>
38
+ )
39
+ }
40
+ ```
41
+
42
+ `appName`, `apiBaseUrl`, and `authUrl` are required. The library does not read environment variables. A Vite application may pass its own values:
43
+
44
+ ```tsx
45
+ <PofsisAuth
46
+ appName={import.meta.env.VITE_POFSIS_APP_NAME}
47
+ apiBaseUrl={import.meta.env.VITE_POFSIS_API_BASE_URL}
48
+ authUrl={import.meta.env.VITE_POFSIS_AUTH_URL}
49
+ scope={import.meta.env.VITE_POFSIS_SCOPE}
50
+ >
51
+ <App />
52
+ </PofsisAuth>
53
+ ```
54
+
55
+ The same props may come from server-injected runtime configuration or hardcoded deployment configuration. The default scope is `openid profile email`.
56
+
57
+ ## Session and login
58
+
59
+ ```tsx
60
+ import { useAuth } from '@pofsis-nbt/pofsis-auth'
61
+
62
+ function AccountMenu() {
63
+ const { user, login, logout } = useAuth()
64
+ if (!user) return <button onClick={() => void login()}>Sign in</button>
65
+ return <button onClick={() => void logout()}>Sign out {user.displayName}</button>
66
+ }
67
+ ```
68
+
69
+ Explicit user actions open hosted Auth in a popup:
70
+
71
+ ```tsx
72
+ const { login } = useAuth()
73
+ <button onClick={() => void login({ presentation: 'popup', returnUrl: location.pathname })}>
74
+ Sign in
75
+ </button>
76
+ ```
77
+
78
+ The provider handles `/login`, `/auth/callback`, and `/auth/error`. Login supports `screen`, `loginHint`, `method`, and a safe application-relative `returnUrl`. An OTP signup opens the signup page and continues to account verification after the identifier is submitted. See [Documentation.md](./Documentation.md) for the complete API.
79
+
80
+ ## Axios
81
+
82
+ ```ts
83
+ import axios from 'axios'
84
+ import { attachAuthInterceptor, useAuth } from '@pofsis-nbt/pofsis-auth'
85
+
86
+ const api = axios.create({ baseURL: '/api' })
87
+
88
+ function ApiAuthBridge() {
89
+ const { getAccessToken, refreshSession } = useAuth()
90
+ React.useEffect(
91
+ () => attachAuthInterceptor(api, getAccessToken, refreshSession),
92
+ [getAccessToken, refreshSession],
93
+ )
94
+ return null
95
+ }
96
+ ```
97
+
98
+ ## CommonJS
99
+
100
+ ```js
101
+ const { PofsisAuth, RequireAuth, useAuth } = require('@pofsis-nbt/pofsis-auth')
102
+ require('@pofsis-nbt/pofsis-auth/style.css')
103
+ ```
104
+
105
+ JavaScript consumers use the compiled JavaScript. TypeScript consumers use the same imports and automatically receive declarations.
106
+
107
+ ## Development
108
+
109
+ Requires Node `^20.19.0` or `>=22.12.0`.
110
+
111
+ ```bash
112
+ npm install
113
+ npm run format:check
114
+ npm run typecheck
115
+ npm test
116
+ npm run build
117
+ npm run pack:check
118
+ ```
119
+
120
+ Build output is written to `dist/` with ESM (`index.js`), CommonJS (`index.cjs`), source maps, declarations, and `style.css`.
121
+
122
+ ## Migration
123
+
124
+ When migrating from `@pofsis/auth-react`:
125
+
126
+ 1. Change imports to `@pofsis-nbt/pofsis-auth`.
127
+ 2. Pass `appName`, `apiBaseUrl`, and `authUrl` to `PofsisAuth`.
128
+ 3. Import `@pofsis-nbt/pofsis-auth/style.css` once.
129
+
130
+ Do not import `src/`, `dist/` internals, or internal auth helpers. Only the package-root exports and `style.css` are supported.
@@ -1,7 +1,3 @@
1
1
  import type { PofsisAuthProps } from './types';
2
- /**
3
- * Single integration surface for tool apps.
4
- * Handles session restore, PKCE login, and fixed paths `/login`, `/auth/callback`, `/auth/error`.
5
- */
6
- export declare function PofsisAuth({ children, appName, apiBaseUrl, authUrl, scope, postLoginPath, components, }: PofsisAuthProps): import("react").JSX.Element;
2
+ export declare function PofsisAuth({ children, appName, apiBaseUrl, authUrl, scope, callbackPath, popup, postLoginPath, components, }: PofsisAuthProps): import("react").JSX.Element;
7
3
  //# sourceMappingURL=PofsisAuth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PofsisAuth.d.ts","sourceRoot":"","sources":["../src/PofsisAuth.tsx"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAIR,eAAe,EAElB,MAAM,SAAS,CAAA;AAmVhB;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EACvB,QAAQ,EACR,OAAO,EACP,UAAU,EACV,OAAO,EACP,KAAK,EACL,aAAmB,EACnB,UAAU,GACb,EAAE,eAAe,+BA4BjB"}
1
+ {"version":3,"file":"PofsisAuth.d.ts","sourceRoot":"","sources":["../src/PofsisAuth.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAGR,eAAe,EAGlB,MAAM,SAAS,CAAA;AAiMhB,wBAAgB,UAAU,CAAC,EACvB,QAAQ,EACR,OAAO,EACP,UAAU,EACV,OAAO,EACP,KAAK,EACL,YAAY,EACZ,KAAK,EACL,aAAmB,EACnB,UAAU,GACb,EAAE,eAAe,+BAiCjB"}
@@ -0,0 +1,2 @@
1
+ async function e(e){if(e.status===204)return null;let t=await e.text();return t?JSON.parse(t):null}async function t(t,n,r){let i=await fetch(`${t.apiBaseUrl}${n}`,{credentials:`include`,...r,headers:{Accept:`application/json`,...r?.body&&!(r.body instanceof FormData)?{"Content-Type":`application/json`}:{},...r?.headers}});if(!i.ok){let e=Error(`Auth API ${n} failed (${i.status})`);throw e.status=i.status,e}return{status:i.status,data:await e(i)}}async function n(e){let{data:n}=await t(e,`/oauth/clients/${encodeURIComponent(e.appName.toUpperCase())}?origin=${encodeURIComponent(window.location.origin)}`);return n}async function r(e,n,r,i){let{data:a}=await t(e,`/oauth/token`,{method:`POST`,body:JSON.stringify({grant_type:`authorization_code`,client_id:i.client_id,redirect_uri:i.redirect_uri,code:n,code_verifier:r})});return a}async function i(e,n){let{data:r}=await t(e,`/oauth/token`,{method:`POST`,body:JSON.stringify({grant_type:`refresh_token`,client_id:n})});return r||null}async function a(e){try{return await t(e,`/auth/session`),!0}catch{return!1}}async function o(e){await t(e,`/auth/logout`,{method:`POST`})}async function s(e,n){let{data:r}=await t(e,`/auth/profile/me`,{headers:{Authorization:`Bearer ${n}`}});return r}async function c(e,n,r){let{data:i}=await t(e,`/auth/profile/me`,{method:`PATCH`,headers:{Authorization:`Bearer ${n}`},body:JSON.stringify(r)});return i}async function l(e,n,r,i){let a=new FormData;a.append(`file`,i);let{data:o}=await t(e,`/auth/profile/me/images/${r}`,{method:`POST`,headers:{Authorization:`Bearer ${n}`},body:a});return o}async function u(e,n,r){let{data:i}=await t(e,`/auth/profile/me/images/${r}`,{method:`DELETE`,headers:{Authorization:`Bearer ${n}`}});return i}function d(e){return e.replace(/\/$/,``)}function f(e={}){let t=e.appName?.trim(),n=e.apiBaseUrl?.trim(),r=e.authUrl?.trim(),i=e.scope?.trim()||`openid profile email`,a=e.callbackPath?.trim()||`/auth/callback`,o={width:e.popup?.width??480,height:e.popup?.height??720,name:e.popup?.name?.trim()||`pofsis-auth`,timeoutMs:e.popup?.timeoutMs??3e5},s=[];if(t||s.push(`appName`),n||s.push(`apiBaseUrl`),r||s.push(`authUrl`),s.length)throw Error(`@pofsis-nbt/pofsis-auth missing config: ${s.join(`, `)}`);if(!t||!n||!r)throw Error(`@pofsis-nbt/pofsis-auth missing config`);if(!a.startsWith(`/`)||a.startsWith(`//`))throw Error(`@pofsis-nbt/pofsis-auth callbackPath must be application-relative`);if(o.width<=0||o.height<=0||o.timeoutMs<=0)throw Error(`@pofsis-nbt/pofsis-auth popup dimensions and timeout must be positive`);return{appName:t,apiBaseUrl:d(n),authUrl:d(r),scope:i,callbackPath:a,popup:o}}var p=/^[^\s@]+@[^\s@]+\.[^\s@]+$/,m=/^(?:\+63|63|0)?9\d{9}$/,h=new Set([`password`,`passkey`,`otp`]),g=new Set([`login`,`signup`,`reset`]),_=320;function v(e,t=!1){let n=typeof e==`string`?e.trim():``;if(!n||n.length>_)return;if(p.test(n))return n.toLowerCase();let r=n.replace(/[\s()-]/g,``);if(!t&&m.test(r))return`+63${r.replace(/^(?:\+63|63|0)/,``)}`}function y(e,t=`login`){let n=typeof e==`string`?{returnUrl:e}:e||{},r=n.screen===`register`?`signup`:n.screen,i=g.has(r)?r:t,a=h.has(n.method)?n.method:void 0,o=i===`login`||i===`signup`&&(a===`password`||a===`otp`)?a:void 0;return{returnUrl:n.returnUrl,screen:i,loginHint:v(n.loginHint),method:o}}function b(e){let t=new URLSearchParams(e),n=t.get(`login_hint`),r=n===null?v(t.get(`email`),!0):v(n);return y({screen:t.get(`screen`)||void 0,loginHint:r,method:t.get(`method`)||void 0})}function x({authUrl:e,appName:t,query:n,options:r}){let i=y(r),a=new URL(e);if(a.search=n.toString(),i.loginHint&&a.searchParams.set(`login_hint`,i.loginHint),i.screen===`login`&&i.method&&a.searchParams.set(`method`,i.method),i.screen===`login`)return a.toString();let o=i.screen===`signup`?`/signup`:`/forgot-password`,s=new URL(o,a);return s.searchParams.set(`app`,t),s.searchParams.set(`return_to`,`${a.pathname}${a.search}`),i.loginHint&&s.searchParams.set(`login_hint`,i.loginHint),i.screen===`signup`&&s.searchParams.set(`method`,i.method||`password`),s.toString()}var S=`pofsis-auth:callback`;function C(e){if(!e)return;let t=e.indexOf(`.`);return t>0?e.slice(0,t):void 0}function w(e=window.location.origin){let t=window.opener&&!window.opener.closed?window.opener:window.parent===window?null:window.parent;if(!t)return!1;let n=new URLSearchParams(window.location.search),r=n.get(`state`)||void 0,i={type:S,code:n.get(`code`)||void 0,state:r,error:n.get(`error`)||void 0,correlationId:C(r||null)};return t.postMessage(i,e),t===window.opener&&window.close(),!0}var T=e=>btoa(String.fromCharCode(...e)).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/,``),E=()=>T(crypto.getRandomValues(new Uint8Array(32)));async function D(e){return T(new Uint8Array(await crypto.subtle.digest(`SHA-256`,new TextEncoder().encode(e))))}var O=e=>e?.startsWith(`/`)&&!e.startsWith(`//`)?e:void 0,k=e=>({token_type:e.token_type,expires_in:e.expires_in,user:e.user}),A=class{config;token=null;session=null;client=null;clientPromise=null;refreshPromise=null;subscribers=new Set;attempt=null;iframeAttempt=null;listening=!1;constructor(e){this.config=f(e),this.attach()}attach(){this.listening||=(window.addEventListener(`message`,this.onPopupMessage),!0)}destroy(){window.removeEventListener(`message`,this.onPopupMessage),this.listening=!1;let e=this.finishAttempt();e&&(e.popup.close(),e.resolve({status:`cancelled`})),this.cancelIframeLogin(),this.subscribers.clear()}getUser(){return this.session?.user??null}getSession(){return this.session}getAccessToken(){return this.token}subscribe(e){return this.attach(),this.subscribers.add(e),e(this.session),()=>{this.subscribers.delete(e)}}emit(){for(let e of this.subscribers)e(this.session)}accept(e){this.token=e.access_token,this.session=k(e),this.emit()}loadClient(){return this.clientPromise||=n(this.config).then(e=>this.client=e).catch(e=>{throw this.clientPromise=null,e}),this.clientPromise}getOAuthClient(){return this.loadClient()}async refreshSession(){let e=await this.loadClient();return this.refreshPromise||=i(this.config,e.client_id).then(e=>{e?this.accept(e):this.clearSession()}).finally(()=>this.refreshPromise=null),this.refreshPromise}async isRemoteSessionActive(){return a(this.config)}clearSession(){this.token=null,this.session=null,this.emit()}replaceUser(e){this.session&&(this.session={...this.session,user:e},this.emit())}async logout(){try{await o(this.config)}finally{this.clearSession()}}signup(e={}){return this.login({...e,screen:`signup`})}resetPassword(e={}){return this.login({...e,screen:`reset`})}async login(e={}){let t=y(e),n=e.presentation??`popup`;return n===`popup`?this.openPopup({...e,...t}):n===`iframe`?this.openIframe({...e,...t}):this.startRedirect({...e,...t})}cancelIframeLogin(){this.finishIframeAttempt()?.resolve({status:`cancelled`})}openIframe(e){let t=e.target;if(!t)return Promise.reject(Error(`POFSIS Auth iframe target is required.`));this.cancelIframeLogin();let n=E(),r=`${n}.${E()}`,i=E(),a=O(e.returnUrl);return new Promise((o,s)=>{let c=setTimeout(()=>{this.finishIframeAttempt()?.reject(Error(`POFSIS Auth iframe timed out.`))},this.config.popup.timeoutMs);this.iframeAttempt={frame:t,state:r,verifier:i,correlationId:n,returnUrl:a,resolve:o,reject:s,timeout:c},this.authorizationUrl(e,r,i).then(e=>t.contentWindow?.location.replace(e)).catch(e=>{this.finishIframeAttempt()?.reject(e instanceof Error?e:Error(String(e)))})})}async authorizationUrl(e,t,n){let r=await this.loadClient(),i=new URLSearchParams({app:this.config.appName.toUpperCase(),client_id:r.client_id,redirect_uri:r.redirect_uri,response_type:`code`,scope:this.config.scope,state:t,code_challenge:await D(n),code_challenge_method:`S256`});return x({authUrl:this.config.authUrl,appName:this.config.appName.toUpperCase(),query:i,options:e})}storageKey(e){return`${this.config.appName.toLowerCase()}.auth.${e}`}async startRedirect(e){let t=E(),n=E();sessionStorage.setItem(this.storageKey(`state`),t),sessionStorage.setItem(this.storageKey(`verifier`),n);let r=O(e.returnUrl);return r?sessionStorage.setItem(this.storageKey(`return-path`),r):sessionStorage.removeItem(this.storageKey(`return-path`)),window.location.assign(await this.authorizationUrl(e,t,n)),{status:`redirecting`}}async openPopup(e){if(this.attempt&&!this.attempt.popup.closed)return this.attempt.popup.focus(),new Promise((e,t)=>{let n=this.attempt,r=n.resolve,i=n.reject;n.resolve=t=>{r(t),e(t)},n.reject=e=>{i(e),t(e)}});let{width:t,height:n,name:r,timeoutMs:i}=this.config.popup,a=Math.max(0,window.screenX+(window.outerWidth-t)/2),o=Math.max(0,window.screenY+(window.outerHeight-n)/2),s=window.open(`about:blank`,r,`popup=yes,width=${t},height=${n},left=${Math.round(a)},top=${Math.round(o)}`);if(!s)return this.startRedirect({...e,presentation:`redirect`});let c=E(),l=`${c}.${E()}`,u=E(),d=O(e.returnUrl);return new Promise((t,n)=>{let r=setInterval(()=>{s.closed&&this.finishAttempt()?.resolve({status:`cancelled`})},250),a=setTimeout(()=>{let e=this.finishAttempt();s.close(),e?.reject(Error(`POFSIS Auth popup timed out.`))},i);this.attempt={popup:s,state:l,verifier:u,correlationId:c,returnUrl:d,resolve:t,reject:n,timer:r,timeout:a},this.authorizationUrl(e,l,u).then(e=>s.location.replace(e)).catch(e=>{let t=this.finishAttempt();s.close(),t?.reject(e instanceof Error?e:Error(String(e)))})})}finishAttempt(){let e=this.attempt;return e?(clearInterval(e.timer),clearTimeout(e.timeout),this.attempt=null,e):null}finishIframeAttempt(){let e=this.iframeAttempt;return e?(clearTimeout(e.timeout),this.iframeAttempt=null,e):null}onPopupMessage=e=>{let t=this.iframeAttempt;if(t&&e.origin===window.location.origin&&e.source===t.frame.contentWindow&&e.data?.type===`pofsis-auth:callback`&&e.data.correlationId===t.correlationId&&C(e.data.state||null)===t.correlationId&&e.data.state===t.state){let t=this.finishIframeAttempt();if(e.data.error||!e.data.code){t.reject(Error(e.data.error||`POFSIS Auth callback was incomplete.`));return}this.completeCode(e.data.code,e.data.state,t.verifier).then(()=>t.resolve({status:`authenticated`,user:this.getUser()})).catch(e=>t.reject(e instanceof Error?e:Error(String(e))));return}let n=this.attempt,r=e.data;if(!n||e.origin!==window.location.origin||e.source!==n.popup||r?.type!==`pofsis-auth:callback`||r.correlationId!==n.correlationId||C(r.state||null)!==n.correlationId||r.state!==n.state)return;let i=this.finishAttempt();if(i){if(r.error||!r.code){i.popup.close(),i.reject(Error(r.error||`POFSIS Auth callback was incomplete.`));return}this.completeCode(r.code,r.state,i.verifier).then(()=>{i.popup.close(),i.returnUrl&&window.history.replaceState(null,``,i.returnUrl),i.resolve({status:`authenticated`,user:this.getUser()})}).catch(e=>{i.popup.close(),i.reject(e instanceof Error?e:Error(String(e)))})}};async completeCode(e,t,n){if(!t||!n)throw Error(`Authentication state could not be verified.`);let i=await this.loadClient();this.accept(await r(this.config,e,n,i))}async completeRedirectCallback(e,t){let n=sessionStorage.getItem(this.storageKey(`state`)),r=sessionStorage.getItem(this.storageKey(`verifier`));if(sessionStorage.removeItem(this.storageKey(`state`)),sessionStorage.removeItem(this.storageKey(`verifier`)),!n||!r||t!==n)throw Error(`Authentication state could not be verified.`);await this.completeCode(e,t,r)}};function j(e){return new A(e)}Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return f}}),Object.defineProperty(exports,"c",{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return b}}),Object.defineProperty(exports,"l",{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return j}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return w}}),Object.defineProperty(exports,"s",{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return A}});
2
+ //# sourceMappingURL=PofsisAuthClient-CRYYsJYc.cjs.map