@eventuras/fides-auth-store 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/LICENSE +21 -0
- package/README.md +55 -0
- package/dist/configure-logger.d.ts +15 -0
- package/dist/configure-logger.d.ts.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +327 -0
- package/dist/index.js.map +1 -0
- package/dist/session-monitor.d.ts +51 -0
- package/dist/session-monitor.d.ts.map +1 -0
- package/dist/store.d.ts +81 -0
- package/dist/store.d.ts.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Losol AS
|
|
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,55 @@
|
|
|
1
|
+
# @eventuras/fides-auth-store
|
|
2
|
+
|
|
3
|
+
Framework-agnostic authentication **state** for [`@eventuras/fides-auth`](../fides-auth),
|
|
4
|
+
built on [XState Store](https://stately.ai/docs/xstate-store).
|
|
5
|
+
|
|
6
|
+
It holds who is logged in, admin status, and error state, plus the logic to keep
|
|
7
|
+
that state in sync — with no dependency on any UI framework or server runtime.
|
|
8
|
+
The application supplies a `checkAuthStatus` callback; the store never reads
|
|
9
|
+
cookies or talks to a server itself, so it can be driven from Next.js, React
|
|
10
|
+
Router, or plain JavaScript alike.
|
|
11
|
+
|
|
12
|
+
React bindings (hooks) are layered on top of this package separately.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm add @eventuras/fides-auth-store
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import {
|
|
24
|
+
configureAuthLogger,
|
|
25
|
+
createAuthStore,
|
|
26
|
+
initializeAuth,
|
|
27
|
+
startSessionMonitor,
|
|
28
|
+
} from '@eventuras/fides-auth-store';
|
|
29
|
+
|
|
30
|
+
// Wire fides-auth's internal logging to @eventuras/logger (once, at startup).
|
|
31
|
+
configureAuthLogger();
|
|
32
|
+
|
|
33
|
+
export const authStore = createAuthStore({
|
|
34
|
+
checkAuthStatus: getAuthStatus, // () => Promise<AuthStatus>
|
|
35
|
+
config: { adminRole: 'Admin', loggerNamespace: 'myapp:auth' },
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Populate the store from the current auth status.
|
|
39
|
+
await initializeAuth(authStore, getAuthStatus);
|
|
40
|
+
|
|
41
|
+
// Poll for session changes; returns a cleanup function.
|
|
42
|
+
const stop = startSessionMonitor(authStore, getAuthStatus, {
|
|
43
|
+
interval: 30_000,
|
|
44
|
+
onSessionExpired: () => console.warn('Session expired'),
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## API
|
|
49
|
+
|
|
50
|
+
- `createAuthStore(options)` — create the store.
|
|
51
|
+
- `initializeAuth(store, checkAuthStatus)` — set initial state from a status check.
|
|
52
|
+
- `checkAuth(store, checkAuthStatus)` — re-check and update state.
|
|
53
|
+
- `startSessionMonitor(store, checkAuthStatus, config)` — poll on an interval; returns a cleanup function.
|
|
54
|
+
- `configureAuthLogger()` — route `@eventuras/fides-auth` logging through `@eventuras/logger`.
|
|
55
|
+
- Types: `AuthStoreContext`, `AuthStoreConfig`, `SessionMonitorConfig`, `SessionUser`, `AuthStatus`.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configures fides-auth to use @eventuras/logger for all internal logging.
|
|
3
|
+
*
|
|
4
|
+
* Call this once at application startup, before creating the auth store.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* import { configureAuthLogger, createAuthStore } from '@eventuras/fides-auth-store';
|
|
9
|
+
*
|
|
10
|
+
* configureAuthLogger();
|
|
11
|
+
* const authStore = createAuthStore({ checkAuthStatus });
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export declare function configureAuthLogger(): void;
|
|
15
|
+
//# sourceMappingURL=configure-logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configure-logger.d.ts","sourceRoot":"","sources":["../src/configure-logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,wBAAgB,mBAAmB,IAAI,IAAI,CAM1C"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-agnostic authentication state store.
|
|
3
|
+
*
|
|
4
|
+
* Built on XState Store, this package holds authentication *state* (who is logged
|
|
5
|
+
* in, admin status, errors) and the logic to keep it in sync — with no dependency
|
|
6
|
+
* on any UI framework or server runtime. The app supplies a `checkAuthStatus`
|
|
7
|
+
* callback; the store does not read cookies or talk to a server itself, so it can
|
|
8
|
+
* be driven from Next.js, React Router, or plain JavaScript alike.
|
|
9
|
+
*
|
|
10
|
+
* React bindings (hooks) live in a separate package layered on top of this one.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import {
|
|
15
|
+
* configureAuthLogger,
|
|
16
|
+
* createAuthStore,
|
|
17
|
+
* initializeAuth,
|
|
18
|
+
* startSessionMonitor,
|
|
19
|
+
* } from '@eventuras/fides-auth-store';
|
|
20
|
+
*
|
|
21
|
+
* configureAuthLogger();
|
|
22
|
+
*
|
|
23
|
+
* export const authStore = createAuthStore({
|
|
24
|
+
* checkAuthStatus: getAuthStatus,
|
|
25
|
+
* config: { adminRole: 'Admin', loggerNamespace: 'myapp:auth' },
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* await initializeAuth(authStore, getAuthStatus);
|
|
29
|
+
*
|
|
30
|
+
* const stop = startSessionMonitor(authStore, getAuthStatus, {
|
|
31
|
+
* interval: 30_000,
|
|
32
|
+
* onSessionExpired: () => console.warn('Session expired'),
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export { createAuthStore, initializeAuth, checkAuth, type AuthStoreContext, type AuthStoreConfig, } from './store';
|
|
37
|
+
export { startSessionMonitor, type SessionMonitorConfig, } from './session-monitor';
|
|
38
|
+
export { configureAuthLogger } from './configure-logger';
|
|
39
|
+
export type { SessionUser, AuthStatus } from './types';
|
|
40
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EACL,eAAe,EACf,cAAc,EACd,SAAS,EACT,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,mBAAmB,EACnB,KAAK,oBAAoB,GAC1B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { createStore } from "@xstate/store";
|
|
2
|
+
import { Logger } from "@eventuras/logger";
|
|
3
|
+
import { configureLogger } from "@eventuras/fides-auth/logger";
|
|
4
|
+
//#region src/store.ts
|
|
5
|
+
/**
|
|
6
|
+
* Default configuration values
|
|
7
|
+
*/
|
|
8
|
+
var DEFAULT_CONFIG$1 = {
|
|
9
|
+
adminRole: "Admin",
|
|
10
|
+
loggerNamespace: "fides:auth-store"
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Creates an authentication store with XState Store
|
|
14
|
+
*
|
|
15
|
+
* @param options - Configuration for the auth store
|
|
16
|
+
* @returns Configured XState store
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const authStore = createAuthStore({
|
|
21
|
+
* checkAuthStatus: async () => getAuthStatus(),
|
|
22
|
+
* config: {
|
|
23
|
+
* adminRole: 'Admin',
|
|
24
|
+
* loggerNamespace: 'myapp:auth',
|
|
25
|
+
* }
|
|
26
|
+
* });
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
function createAuthStore(options) {
|
|
30
|
+
const config = {
|
|
31
|
+
...DEFAULT_CONFIG$1,
|
|
32
|
+
...options.config
|
|
33
|
+
};
|
|
34
|
+
const logger = Logger.create({
|
|
35
|
+
namespace: config.loggerNamespace,
|
|
36
|
+
context: { component: "AuthStore" }
|
|
37
|
+
});
|
|
38
|
+
return createStore({
|
|
39
|
+
context: {
|
|
40
|
+
user: null,
|
|
41
|
+
isAdmin: false,
|
|
42
|
+
isAuthenticated: false,
|
|
43
|
+
error: null,
|
|
44
|
+
lastChecked: null,
|
|
45
|
+
isInitializing: true
|
|
46
|
+
},
|
|
47
|
+
on: {
|
|
48
|
+
/**
|
|
49
|
+
* Initialize authentication - check current status
|
|
50
|
+
*/
|
|
51
|
+
initialize: () => ({
|
|
52
|
+
user: null,
|
|
53
|
+
isAdmin: false,
|
|
54
|
+
isAuthenticated: false,
|
|
55
|
+
error: null,
|
|
56
|
+
lastChecked: null,
|
|
57
|
+
isInitializing: true
|
|
58
|
+
}),
|
|
59
|
+
/**
|
|
60
|
+
* Authentication check succeeded
|
|
61
|
+
*/
|
|
62
|
+
authSuccess: (_context, event) => {
|
|
63
|
+
logger.info({ user: event.user.roles }, "User authenticated");
|
|
64
|
+
return {
|
|
65
|
+
user: event.user,
|
|
66
|
+
isAdmin: event.user.roles?.includes(config.adminRole) ?? false,
|
|
67
|
+
isAuthenticated: true,
|
|
68
|
+
error: null,
|
|
69
|
+
lastChecked: /* @__PURE__ */ new Date(),
|
|
70
|
+
isInitializing: false
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
/**
|
|
74
|
+
* Authentication check failed - not authenticated
|
|
75
|
+
*/
|
|
76
|
+
authFailed: (_context, event = {}) => {
|
|
77
|
+
logger.info("User not authenticated");
|
|
78
|
+
return {
|
|
79
|
+
user: null,
|
|
80
|
+
isAdmin: false,
|
|
81
|
+
isAuthenticated: false,
|
|
82
|
+
error: event.error || null,
|
|
83
|
+
lastChecked: /* @__PURE__ */ new Date(),
|
|
84
|
+
isInitializing: false
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
/**
|
|
88
|
+
* Session expired
|
|
89
|
+
*/
|
|
90
|
+
sessionExpired: () => {
|
|
91
|
+
logger.warn("Session expired");
|
|
92
|
+
return {
|
|
93
|
+
user: null,
|
|
94
|
+
isAdmin: false,
|
|
95
|
+
isAuthenticated: false,
|
|
96
|
+
error: "Session expired",
|
|
97
|
+
lastChecked: /* @__PURE__ */ new Date(),
|
|
98
|
+
isInitializing: false
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
/**
|
|
102
|
+
* User logged out
|
|
103
|
+
*/
|
|
104
|
+
logout: () => {
|
|
105
|
+
logger.info("User logged out");
|
|
106
|
+
return {
|
|
107
|
+
user: null,
|
|
108
|
+
isAdmin: false,
|
|
109
|
+
isAuthenticated: false,
|
|
110
|
+
error: null,
|
|
111
|
+
lastChecked: /* @__PURE__ */ new Date(),
|
|
112
|
+
isInitializing: false
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
/**
|
|
116
|
+
* Login success
|
|
117
|
+
*/
|
|
118
|
+
loginSuccess: (_context, event) => {
|
|
119
|
+
logger.info({ user: event.user.email }, "Login successful");
|
|
120
|
+
return {
|
|
121
|
+
user: event.user,
|
|
122
|
+
isAdmin: event.user.roles?.includes(config.adminRole) ?? false,
|
|
123
|
+
isAuthenticated: true,
|
|
124
|
+
error: null,
|
|
125
|
+
lastChecked: /* @__PURE__ */ new Date(),
|
|
126
|
+
isInitializing: false
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
/**
|
|
130
|
+
* Update last checked timestamp
|
|
131
|
+
*/
|
|
132
|
+
updateLastChecked: (context) => ({
|
|
133
|
+
...context,
|
|
134
|
+
lastChecked: /* @__PURE__ */ new Date()
|
|
135
|
+
}),
|
|
136
|
+
/**
|
|
137
|
+
* Set error
|
|
138
|
+
*/
|
|
139
|
+
setError: (context, event) => ({
|
|
140
|
+
...context,
|
|
141
|
+
error: event.error
|
|
142
|
+
}),
|
|
143
|
+
/**
|
|
144
|
+
* Clear error
|
|
145
|
+
*/
|
|
146
|
+
clearError: (context) => ({
|
|
147
|
+
...context,
|
|
148
|
+
error: null
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Helper function to initialize auth by checking status
|
|
155
|
+
*/
|
|
156
|
+
async function initializeAuth(store, checkAuthStatus) {
|
|
157
|
+
const logger = Logger.create({
|
|
158
|
+
namespace: "fides:auth-store",
|
|
159
|
+
context: { component: "initializeAuth" }
|
|
160
|
+
});
|
|
161
|
+
logger.debug("Initializing authentication");
|
|
162
|
+
store.send({ type: "initialize" });
|
|
163
|
+
try {
|
|
164
|
+
const result = await checkAuthStatus();
|
|
165
|
+
logger.debug({ authenticated: result.authenticated }, "Auth status checked");
|
|
166
|
+
if (result.authenticated && result.user) store.send({
|
|
167
|
+
type: "authSuccess",
|
|
168
|
+
user: result.user
|
|
169
|
+
});
|
|
170
|
+
else store.send({ type: "authFailed" });
|
|
171
|
+
} catch (error) {
|
|
172
|
+
logger.debug({ error }, "Auth check returned no session");
|
|
173
|
+
store.send({ type: "authFailed" });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Helper function to check auth status and update store
|
|
178
|
+
*/
|
|
179
|
+
async function checkAuth(store, checkAuthStatus) {
|
|
180
|
+
const logger = Logger.create({
|
|
181
|
+
namespace: "fides:auth-store",
|
|
182
|
+
context: { component: "checkAuth" }
|
|
183
|
+
});
|
|
184
|
+
logger.debug("Checking authentication status");
|
|
185
|
+
try {
|
|
186
|
+
const result = await checkAuthStatus();
|
|
187
|
+
logger.debug({ authenticated: result.authenticated }, "Auth status checked");
|
|
188
|
+
if (result.authenticated && result.user) store.send({
|
|
189
|
+
type: "authSuccess",
|
|
190
|
+
user: result.user
|
|
191
|
+
});
|
|
192
|
+
else store.send({ type: "authFailed" });
|
|
193
|
+
store.send({ type: "updateLastChecked" });
|
|
194
|
+
} catch (error) {
|
|
195
|
+
const err = error;
|
|
196
|
+
if (err?.code === "OAUTH_RESPONSE_BODY_ERROR" && err?.error === "invalid_grant") {
|
|
197
|
+
logger.info("Session ended - refresh token expired or invalid");
|
|
198
|
+
store.send({ type: "sessionExpired" });
|
|
199
|
+
} else {
|
|
200
|
+
const errorDetails = { message: error instanceof Error ? error.message : "Unknown error" };
|
|
201
|
+
if (error && typeof error === "object") {
|
|
202
|
+
const oauthError = error;
|
|
203
|
+
if (oauthError.code) errorDetails.code = oauthError.code;
|
|
204
|
+
if (oauthError.error) errorDetails.error = oauthError.error;
|
|
205
|
+
if (oauthError.error_description) errorDetails.error_description = oauthError.error_description;
|
|
206
|
+
if (oauthError.status) errorDetails.status = oauthError.status;
|
|
207
|
+
}
|
|
208
|
+
logger.error(errorDetails, "Auth check error");
|
|
209
|
+
store.send({
|
|
210
|
+
type: "authFailed",
|
|
211
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
//#endregion
|
|
217
|
+
//#region src/session-monitor.ts
|
|
218
|
+
var DEFAULT_CONFIG = {
|
|
219
|
+
interval: 3e4,
|
|
220
|
+
loggerNamespace: "fides:session-monitor"
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* Creates a session monitor that polls auth status at regular intervals
|
|
224
|
+
*
|
|
225
|
+
* This is designed to be used with React's useEffect for cleanup.
|
|
226
|
+
*
|
|
227
|
+
* @param store - The auth store to update
|
|
228
|
+
* @param checkAuthStatus - Function to check authentication status
|
|
229
|
+
* @param config - Optional configuration
|
|
230
|
+
* @returns Cleanup function to stop monitoring
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```typescript
|
|
234
|
+
* useEffect(() => {
|
|
235
|
+
* const cleanup = startSessionMonitor(authStore, checkAuthStatus, {
|
|
236
|
+
* interval: 30_000,
|
|
237
|
+
* onSessionExpired: () => {
|
|
238
|
+
* toast.warn('Your session has expired');
|
|
239
|
+
* }
|
|
240
|
+
* });
|
|
241
|
+
*
|
|
242
|
+
* return cleanup;
|
|
243
|
+
* }, []);
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
246
|
+
function startSessionMonitor(store, checkAuthStatus, config = {}) {
|
|
247
|
+
const finalConfig = {
|
|
248
|
+
...DEFAULT_CONFIG,
|
|
249
|
+
...config
|
|
250
|
+
};
|
|
251
|
+
const logger = Logger.create({
|
|
252
|
+
namespace: finalConfig.loggerNamespace,
|
|
253
|
+
context: { component: "SessionMonitor" }
|
|
254
|
+
});
|
|
255
|
+
logger.debug({ interval: finalConfig.interval }, "Starting session monitor");
|
|
256
|
+
let isActive = true;
|
|
257
|
+
let timeoutId = null;
|
|
258
|
+
const poll = async () => {
|
|
259
|
+
if (!isActive) return;
|
|
260
|
+
try {
|
|
261
|
+
const result = await checkAuthStatus();
|
|
262
|
+
logger.debug({ authenticated: result.authenticated }, "Session monitor check");
|
|
263
|
+
if (!result.authenticated) {
|
|
264
|
+
logger.info("Session monitor detected unauthenticated state - session ended");
|
|
265
|
+
store.send({ type: "sessionExpired" });
|
|
266
|
+
if (finalConfig.onSessionExpired) finalConfig.onSessionExpired();
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (result.user) store.send({
|
|
270
|
+
type: "authSuccess",
|
|
271
|
+
user: result.user
|
|
272
|
+
});
|
|
273
|
+
} catch (error) {
|
|
274
|
+
const err = error;
|
|
275
|
+
if (err?.code === "OAUTH_RESPONSE_BODY_ERROR" && err?.error === "invalid_grant") {
|
|
276
|
+
logger.info("Session monitor detected expired refresh token - user session ended");
|
|
277
|
+
store.send({ type: "sessionExpired" });
|
|
278
|
+
if (finalConfig.onSessionExpired) finalConfig.onSessionExpired();
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const errorDetails = { message: error instanceof Error ? error.message : "Unknown error" };
|
|
282
|
+
if (error && typeof error === "object") {
|
|
283
|
+
const oauthError = error;
|
|
284
|
+
if (oauthError.code) errorDetails.code = oauthError.code;
|
|
285
|
+
if (oauthError.error) errorDetails.error = oauthError.error;
|
|
286
|
+
if (oauthError.error_description) errorDetails.error_description = oauthError.error_description;
|
|
287
|
+
if (oauthError.status) errorDetails.status = oauthError.status;
|
|
288
|
+
}
|
|
289
|
+
logger.error(errorDetails, "Session monitor error");
|
|
290
|
+
if (finalConfig.onError && error instanceof Error) finalConfig.onError(error);
|
|
291
|
+
}
|
|
292
|
+
if (isActive) timeoutId = setTimeout(poll, finalConfig.interval);
|
|
293
|
+
};
|
|
294
|
+
timeoutId = setTimeout(poll, finalConfig.interval);
|
|
295
|
+
return () => {
|
|
296
|
+
logger.debug("Stopping session monitor");
|
|
297
|
+
isActive = false;
|
|
298
|
+
if (timeoutId) {
|
|
299
|
+
clearTimeout(timeoutId);
|
|
300
|
+
timeoutId = null;
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
//#endregion
|
|
305
|
+
//#region src/configure-logger.ts
|
|
306
|
+
/**
|
|
307
|
+
* Configures fides-auth to use @eventuras/logger for all internal logging.
|
|
308
|
+
*
|
|
309
|
+
* Call this once at application startup, before creating the auth store.
|
|
310
|
+
*
|
|
311
|
+
* @example
|
|
312
|
+
* ```typescript
|
|
313
|
+
* import { configureAuthLogger, createAuthStore } from '@eventuras/fides-auth-store';
|
|
314
|
+
*
|
|
315
|
+
* configureAuthLogger();
|
|
316
|
+
* const authStore = createAuthStore({ checkAuthStatus });
|
|
317
|
+
* ```
|
|
318
|
+
*/
|
|
319
|
+
function configureAuthLogger() {
|
|
320
|
+
configureLogger({ create(options) {
|
|
321
|
+
return Logger.create(options);
|
|
322
|
+
} });
|
|
323
|
+
}
|
|
324
|
+
//#endregion
|
|
325
|
+
export { checkAuth, configureAuthLogger, createAuthStore, initializeAuth, startSessionMonitor };
|
|
326
|
+
|
|
327
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/store.ts","../src/session-monitor.ts","../src/configure-logger.ts"],"sourcesContent":["import { createStore } from '@xstate/store';\nimport { Logger } from '@eventuras/logger';\nimport type { SessionUser, AuthStatus } from './types';\n\n/**\n * Authentication store context\n */\nexport type AuthStoreContext = {\n user: SessionUser | null;\n isAdmin: boolean;\n isAuthenticated: boolean;\n error: string | null;\n lastChecked: Date | null;\n isInitializing: boolean;\n};\n\n/**\n * Configuration for the auth store\n */\nexport type AuthStoreConfig = {\n /**\n * Function to check current authentication status\n */\n checkAuthStatus: () => Promise<AuthStatus>;\n\n /**\n * Optional configuration\n */\n config?: {\n /**\n * Role name that indicates admin access\n * @default 'Admin'\n */\n adminRole?: string;\n\n /**\n * Namespace for logger\n * @default 'fides-auth-next:store'\n */\n loggerNamespace?: string;\n };\n};\n\n/**\n * Default configuration values\n */\nconst DEFAULT_CONFIG = {\n adminRole: 'Admin',\n loggerNamespace: 'fides:auth-store',\n};\n\n/**\n * Creates an authentication store with XState Store\n *\n * @param options - Configuration for the auth store\n * @returns Configured XState store\n *\n * @example\n * ```typescript\n * const authStore = createAuthStore({\n * checkAuthStatus: async () => getAuthStatus(),\n * config: {\n * adminRole: 'Admin',\n * loggerNamespace: 'myapp:auth',\n * }\n * });\n * ```\n */\nexport function createAuthStore(options: AuthStoreConfig) {\n const config = { ...DEFAULT_CONFIG, ...options.config };\n const logger = Logger.create({\n namespace: config.loggerNamespace,\n context: { component: 'AuthStore' },\n });\n\n /**\n * Initial context values\n */\n const initialContext: AuthStoreContext = {\n user: null,\n isAdmin: false,\n isAuthenticated: false,\n error: null,\n lastChecked: null,\n isInitializing: true,\n };\n\n /**\n * Create the store with events\n */\n const store = createStore(\n {\n context: initialContext,\n on: {\n /**\n * Initialize authentication - check current status\n */\n initialize: () => ({\n user: null,\n isAdmin: false,\n isAuthenticated: false,\n error: null,\n lastChecked: null,\n isInitializing: true,\n }),\n\n /**\n * Authentication check succeeded\n */\n authSuccess: (_context: AuthStoreContext, event: { user: SessionUser; }) => {\n logger.info({ user: event.user.roles }, 'User authenticated');\n return {\n user: event.user,\n isAdmin: event.user.roles?.includes(config.adminRole) ?? false,\n isAuthenticated: true,\n error: null,\n lastChecked: new Date(),\n isInitializing: false,\n };\n },\n\n /**\n * Authentication check failed - not authenticated\n */\n authFailed: (_context: AuthStoreContext, event: { error?: string; } = {}) => {\n logger.info('User not authenticated');\n return {\n user: null,\n isAdmin: false,\n isAuthenticated: false,\n error: event.error || null,\n lastChecked: new Date(),\n isInitializing: false,\n };\n },\n\n /**\n * Session expired\n */\n sessionExpired: () => {\n logger.warn('Session expired');\n return {\n user: null,\n isAdmin: false,\n isAuthenticated: false,\n error: 'Session expired',\n lastChecked: new Date(),\n isInitializing: false,\n };\n },\n\n /**\n * User logged out\n */\n logout: () => {\n logger.info('User logged out');\n return {\n user: null,\n isAdmin: false,\n isAuthenticated: false,\n error: null,\n lastChecked: new Date(),\n isInitializing: false,\n };\n },\n\n /**\n * Login success\n */\n loginSuccess: (_context: AuthStoreContext, event: { user: SessionUser; }) => {\n logger.info({ user: event.user.email }, 'Login successful');\n return {\n user: event.user,\n isAdmin: event.user.roles?.includes(config.adminRole) ?? false,\n isAuthenticated: true,\n error: null,\n lastChecked: new Date(),\n isInitializing: false,\n };\n },\n\n /**\n * Update last checked timestamp\n */\n updateLastChecked: (context: AuthStoreContext) => ({\n ...context,\n lastChecked: new Date(),\n }),\n\n /**\n * Set error\n */\n setError: (context: AuthStoreContext, event: { error: string; }) => ({\n ...context,\n error: event.error,\n }),\n\n /**\n * Clear error\n */\n clearError: (context: AuthStoreContext) => ({\n ...context,\n error: null,\n }),\n },\n }\n );\n\n return store;\n}\n\n/**\n * Helper function to initialize auth by checking status\n */\nexport async function initializeAuth(\n store: ReturnType<typeof createAuthStore>,\n checkAuthStatus: () => Promise<AuthStatus>\n) {\n const logger = Logger.create({\n namespace: 'fides:auth-store',\n context: { component: 'initializeAuth' },\n });\n\n logger.debug('Initializing authentication');\n store.send({ type: 'initialize' });\n\n try {\n const result = await checkAuthStatus();\n logger.debug({ authenticated: result.authenticated }, 'Auth status checked');\n\n if (result.authenticated && result.user) {\n store.send({ type: 'authSuccess', user: result.user });\n } else {\n store.send({ type: 'authFailed' });\n }\n } catch (error) {\n logger.debug({ error }, 'Auth check returned no session');\n store.send({ type: 'authFailed' });\n }\n}\n\n/**\n * Helper function to check auth status and update store\n */\nexport async function checkAuth(\n store: ReturnType<typeof createAuthStore>,\n checkAuthStatus: () => Promise<AuthStatus>\n) {\n const logger = Logger.create({\n namespace: 'fides:auth-store',\n context: { component: 'checkAuth' },\n });\n\n logger.debug('Checking authentication status');\n\n try {\n const result = await checkAuthStatus();\n logger.debug({ authenticated: result.authenticated }, 'Auth status checked');\n\n if (result.authenticated && result.user) {\n store.send({ type: 'authSuccess', user: result.user });\n } else {\n store.send({ type: 'authFailed' });\n }\n\n store.send({ type: 'updateLastChecked' });\n } catch (error) {\n // Check if this is an expected invalid_grant error (expired refresh token)\n const err = error as { code?: string; error?: string; error_description?: string; };\n const isInvalidGrant =\n err?.code === 'OAUTH_RESPONSE_BODY_ERROR' && err?.error === 'invalid_grant';\n\n if (isInvalidGrant) {\n // This is expected during logout or session expiry - log at info level\n logger.info('Session ended - refresh token expired or invalid');\n store.send({ type: 'sessionExpired' });\n } else {\n // Unexpected error during auth check - log with details\n const errorDetails: Record<string, unknown> = {\n message: error instanceof Error ? error.message : 'Unknown error',\n };\n\n // Extract OAuth error details if present\n if (error && typeof error === 'object') {\n const oauthError = error as any;\n if (oauthError.code) errorDetails.code = oauthError.code;\n if (oauthError.error) errorDetails.error = oauthError.error;\n if (oauthError.error_description)\n errorDetails.error_description = oauthError.error_description;\n if (oauthError.status) errorDetails.status = oauthError.status;\n }\n\n logger.error(errorDetails, 'Auth check error');\n store.send({\n type: 'authFailed',\n error: error instanceof Error ? error.message : 'Unknown error',\n });\n }\n }\n}\n","import { Logger } from '@eventuras/logger';\nimport type { createAuthStore } from './store';\nimport type { AuthStatus } from './types';\n\n/**\n * Configuration for session monitoring\n */\nexport type SessionMonitorConfig = {\n /**\n * Interval in milliseconds for session monitoring polling\n * @default 30000 (30 seconds)\n */\n interval?: number;\n\n /**\n * Namespace for logger\n * @default 'fides:session-monitor'\n */\n loggerNamespace?: string;\n\n /**\n * Callback when session expires\n */\n onSessionExpired?: () => void;\n\n /**\n * Callback when session check fails\n */\n onError?: (error: Error) => void;\n};\n\nconst DEFAULT_CONFIG = {\n interval: 30_000, // 30 seconds\n loggerNamespace: 'fides:session-monitor',\n};\n\n/**\n * Creates a session monitor that polls auth status at regular intervals\n *\n * This is designed to be used with React's useEffect for cleanup.\n *\n * @param store - The auth store to update\n * @param checkAuthStatus - Function to check authentication status\n * @param config - Optional configuration\n * @returns Cleanup function to stop monitoring\n *\n * @example\n * ```typescript\n * useEffect(() => {\n * const cleanup = startSessionMonitor(authStore, checkAuthStatus, {\n * interval: 30_000,\n * onSessionExpired: () => {\n * toast.warn('Your session has expired');\n * }\n * });\n *\n * return cleanup;\n * }, []);\n * ```\n */\nexport function startSessionMonitor(\n store: ReturnType<typeof createAuthStore>,\n checkAuthStatus: () => Promise<AuthStatus>,\n config: SessionMonitorConfig = {}\n): () => void {\n const finalConfig = { ...DEFAULT_CONFIG, ...config };\n const logger = Logger.create({\n namespace: finalConfig.loggerNamespace,\n context: { component: 'SessionMonitor' },\n });\n\n logger.debug({ interval: finalConfig.interval }, 'Starting session monitor');\n\n let isActive = true;\n // Portable timer handle: `number` in the browser, `NodeJS.Timeout` under Node —\n // this package is framework-agnostic and runs in both.\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n const poll = async (): Promise<void> => {\n if (!isActive) return;\n\n try {\n const result = await checkAuthStatus();\n logger.debug({ authenticated: result.authenticated }, 'Session monitor check');\n\n if (!result.authenticated) {\n // User is no longer authenticated - session ended\n logger.info('Session monitor detected unauthenticated state - session ended');\n store.send({ type: 'sessionExpired' });\n\n if (finalConfig.onSessionExpired) {\n finalConfig.onSessionExpired();\n }\n\n // Stop monitoring after session expires\n return;\n }\n\n if (result.user) {\n store.send({ type: 'authSuccess', user: result.user });\n }\n } catch (error) {\n // Check if this is an expected invalid_grant error (expired refresh token)\n const err = error as { code?: string; error?: string; error_description?: string; };\n const isInvalidGrant =\n err?.code === 'OAUTH_RESPONSE_BODY_ERROR' && err?.error === 'invalid_grant';\n\n if (isInvalidGrant) {\n // This is expected during logout or session expiry - log at info level\n logger.info('Session monitor detected expired refresh token - user session ended');\n store.send({ type: 'sessionExpired' });\n\n if (finalConfig.onSessionExpired) {\n finalConfig.onSessionExpired();\n }\n\n // Stop monitoring after session expires\n return;\n }\n\n // Unexpected error during auth check - log with details\n const errorDetails: Record<string, unknown> = {\n message: error instanceof Error ? error.message : 'Unknown error',\n };\n\n // Extract OAuth error details if present\n if (error && typeof error === 'object') {\n const oauthError = error as any;\n if (oauthError.code) errorDetails.code = oauthError.code;\n if (oauthError.error) errorDetails.error = oauthError.error;\n if (oauthError.error_description)\n errorDetails.error_description = oauthError.error_description;\n if (oauthError.status) errorDetails.status = oauthError.status;\n }\n\n logger.error(errorDetails, 'Session monitor error');\n\n if (finalConfig.onError && error instanceof Error) {\n finalConfig.onError(error);\n }\n }\n\n // Schedule next poll if still active\n if (isActive) {\n timeoutId = setTimeout(poll, finalConfig.interval);\n }\n };\n\n // Start first poll\n timeoutId = setTimeout(poll, finalConfig.interval);\n\n // Return cleanup function\n return () => {\n logger.debug('Stopping session monitor');\n isActive = false;\n if (timeoutId) {\n clearTimeout(timeoutId);\n timeoutId = null;\n }\n };\n}\n","/**\n * Configures fides-auth to use @eventuras/logger for all internal logging.\n *\n * Call this once at application startup, before creating the auth store.\n *\n * @example\n * ```typescript\n * import { configureAuthLogger, createAuthStore } from '@eventuras/fides-auth-store';\n *\n * configureAuthLogger();\n * const authStore = createAuthStore({ checkAuthStatus });\n * ```\n */\n\nimport { configureLogger } from '@eventuras/fides-auth/logger';\nimport { Logger } from '@eventuras/logger';\n\nexport function configureAuthLogger(): void {\n configureLogger({\n create(options) {\n return Logger.create(options);\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { createAuthStore } from './store';
|
|
2
|
+
import { AuthStatus } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for session monitoring
|
|
5
|
+
*/
|
|
6
|
+
export type SessionMonitorConfig = {
|
|
7
|
+
/**
|
|
8
|
+
* Interval in milliseconds for session monitoring polling
|
|
9
|
+
* @default 30000 (30 seconds)
|
|
10
|
+
*/
|
|
11
|
+
interval?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Namespace for logger
|
|
14
|
+
* @default 'fides:session-monitor'
|
|
15
|
+
*/
|
|
16
|
+
loggerNamespace?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Callback when session expires
|
|
19
|
+
*/
|
|
20
|
+
onSessionExpired?: () => void;
|
|
21
|
+
/**
|
|
22
|
+
* Callback when session check fails
|
|
23
|
+
*/
|
|
24
|
+
onError?: (error: Error) => void;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Creates a session monitor that polls auth status at regular intervals
|
|
28
|
+
*
|
|
29
|
+
* This is designed to be used with React's useEffect for cleanup.
|
|
30
|
+
*
|
|
31
|
+
* @param store - The auth store to update
|
|
32
|
+
* @param checkAuthStatus - Function to check authentication status
|
|
33
|
+
* @param config - Optional configuration
|
|
34
|
+
* @returns Cleanup function to stop monitoring
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* useEffect(() => {
|
|
39
|
+
* const cleanup = startSessionMonitor(authStore, checkAuthStatus, {
|
|
40
|
+
* interval: 30_000,
|
|
41
|
+
* onSessionExpired: () => {
|
|
42
|
+
* toast.warn('Your session has expired');
|
|
43
|
+
* }
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* return cleanup;
|
|
47
|
+
* }, []);
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare function startSessionMonitor(store: ReturnType<typeof createAuthStore>, checkAuthStatus: () => Promise<AuthStatus>, config?: SessionMonitorConfig): () => void;
|
|
51
|
+
//# sourceMappingURL=session-monitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-monitor.d.ts","sourceRoot":"","sources":["../src/session-monitor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAC;IAE9B;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CAClC,CAAC;AAOF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,EACzC,eAAe,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,EAC1C,MAAM,GAAE,oBAAyB,GAChC,MAAM,IAAI,CAgGZ"}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { SessionUser, AuthStatus } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Authentication store context
|
|
4
|
+
*/
|
|
5
|
+
export type AuthStoreContext = {
|
|
6
|
+
user: SessionUser | null;
|
|
7
|
+
isAdmin: boolean;
|
|
8
|
+
isAuthenticated: boolean;
|
|
9
|
+
error: string | null;
|
|
10
|
+
lastChecked: Date | null;
|
|
11
|
+
isInitializing: boolean;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Configuration for the auth store
|
|
15
|
+
*/
|
|
16
|
+
export type AuthStoreConfig = {
|
|
17
|
+
/**
|
|
18
|
+
* Function to check current authentication status
|
|
19
|
+
*/
|
|
20
|
+
checkAuthStatus: () => Promise<AuthStatus>;
|
|
21
|
+
/**
|
|
22
|
+
* Optional configuration
|
|
23
|
+
*/
|
|
24
|
+
config?: {
|
|
25
|
+
/**
|
|
26
|
+
* Role name that indicates admin access
|
|
27
|
+
* @default 'Admin'
|
|
28
|
+
*/
|
|
29
|
+
adminRole?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Namespace for logger
|
|
32
|
+
* @default 'fides-auth-next:store'
|
|
33
|
+
*/
|
|
34
|
+
loggerNamespace?: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Creates an authentication store with XState Store
|
|
39
|
+
*
|
|
40
|
+
* @param options - Configuration for the auth store
|
|
41
|
+
* @returns Configured XState store
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* const authStore = createAuthStore({
|
|
46
|
+
* checkAuthStatus: async () => getAuthStatus(),
|
|
47
|
+
* config: {
|
|
48
|
+
* adminRole: 'Admin',
|
|
49
|
+
* loggerNamespace: 'myapp:auth',
|
|
50
|
+
* }
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare function createAuthStore(options: AuthStoreConfig): import('@xstate/store').Store<AuthStoreContext, {
|
|
55
|
+
initialize: unknown;
|
|
56
|
+
authSuccess: {
|
|
57
|
+
user: SessionUser;
|
|
58
|
+
};
|
|
59
|
+
authFailed: {
|
|
60
|
+
error?: string;
|
|
61
|
+
} | undefined;
|
|
62
|
+
sessionExpired: unknown;
|
|
63
|
+
logout: unknown;
|
|
64
|
+
loginSuccess: {
|
|
65
|
+
user: SessionUser;
|
|
66
|
+
};
|
|
67
|
+
updateLastChecked: unknown;
|
|
68
|
+
setError: {
|
|
69
|
+
error: string;
|
|
70
|
+
};
|
|
71
|
+
clearError: unknown;
|
|
72
|
+
}, never>;
|
|
73
|
+
/**
|
|
74
|
+
* Helper function to initialize auth by checking status
|
|
75
|
+
*/
|
|
76
|
+
export declare function initializeAuth(store: ReturnType<typeof createAuthStore>, checkAuthStatus: () => Promise<AuthStatus>): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Helper function to check auth status and update store
|
|
79
|
+
*/
|
|
80
|
+
export declare function checkAuth(store: ReturnType<typeof createAuthStore>, checkAuthStatus: () => Promise<AuthStatus>): Promise<void>;
|
|
81
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEvD;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,OAAO,CAAC;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,cAAc,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B;;OAEG;IACH,eAAe,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC;IAE3C;;OAEG;IACH,MAAM,CAAC,EAAE;QACP;;;WAGG;QACH,SAAS,CAAC,EAAE,MAAM,CAAC;QAEnB;;;WAGG;QACH,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;CACH,CAAC;AAUF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,eAAe;;;cAyCS,WAAW;;;gBAeV,MAAM;;;;;cA6CN,WAAW;;;;eAuBf,MAAM;;;UAiBnE;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,EACzC,eAAe,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,iBAuB3C;AAED;;GAEG;AACH,wBAAsB,SAAS,CAC7B,KAAK,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,EACzC,eAAe,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,iBAqD3C"}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@eventuras/fides-auth-store",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Framework-agnostic authentication state store (XState Store) for @eventuras/fides-auth",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"authentication",
|
|
7
|
+
"oauth",
|
|
8
|
+
"oidc",
|
|
9
|
+
"session",
|
|
10
|
+
"state-management",
|
|
11
|
+
"xstate",
|
|
12
|
+
"store"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://github.com/losol/fides-auth/tree/main/packages/fides-auth-store#readme",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/losol/fides-auth/issues"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/losol/fides-auth.git",
|
|
21
|
+
"directory": "packages/fides-auth-store"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "losol <losol@users.noreply.github.com>",
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"type": "module",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"main": "./dist/index.js",
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"LICENSE",
|
|
38
|
+
"README.md"
|
|
39
|
+
],
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@eventuras/logger": "^0.8.1",
|
|
42
|
+
"@xstate/store": "^4.0.0",
|
|
43
|
+
"@eventuras/fides-auth": "0.9.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^22.20.0",
|
|
47
|
+
"vite": "^8.0.8",
|
|
48
|
+
"vitest": "^4.1.4",
|
|
49
|
+
"@eventuras/typescript-config": "1.0.0",
|
|
50
|
+
"@eventuras/vite-config": "0.2.2"
|
|
51
|
+
},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=18"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "vite build",
|
|
57
|
+
"dev": "vite build --watch",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"test:watch": "vitest"
|
|
60
|
+
}
|
|
61
|
+
}
|