@auditauth/web 0.2.0-beta.1
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/README.md +108 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/sdk.d.ts +21 -0
- package/dist/sdk.js +164 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.js +1 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# @auditauth/web
|
|
2
|
+
|
|
3
|
+
`@auditauth/web` is the framework-agnostic AuditAuth SDK for browser
|
|
4
|
+
applications. It handles login redirects, callback processing, session state,
|
|
5
|
+
token refresh, and request and navigation metrics.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Install the package in your app.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @auditauth/web
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Create an SDK instance
|
|
16
|
+
|
|
17
|
+
Create one `AuditAuthWeb` instance with your AuditAuth config and a storage
|
|
18
|
+
adapter. In browser apps, local storage is the common default.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { AuditAuthWeb } from '@auditauth/web'
|
|
22
|
+
|
|
23
|
+
export const auditauth = new AuditAuthWeb(
|
|
24
|
+
{
|
|
25
|
+
apiKey: process.env.VITE_AUDITAUTH_API_KEY!,
|
|
26
|
+
appId: process.env.VITE_AUDITAUTH_APP_ID!,
|
|
27
|
+
baseUrl: 'http://localhost:5173',
|
|
28
|
+
redirectUrl: 'http://localhost:5173/private',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
get: (name) => localStorage.getItem(name),
|
|
32
|
+
set: (name, value) => localStorage.setItem(name, value),
|
|
33
|
+
remove: (name) => localStorage.removeItem(name),
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Initialize on app startup
|
|
39
|
+
|
|
40
|
+
Handle callback redirects before rendering protected pages, then enable
|
|
41
|
+
navigation tracking.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
await auditauth.handleRedirect()
|
|
45
|
+
auditauth.initNavigationTracking()
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Protect private pages
|
|
49
|
+
|
|
50
|
+
Before rendering private views, check if the user has a session. If not,
|
|
51
|
+
redirect to login.
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
if (!auditauth.isAuthenticated()) {
|
|
55
|
+
await auditauth.login()
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
You can access the current user at any time with `auditauth.getSessionUser()`.
|
|
60
|
+
|
|
61
|
+
## Make authenticated API calls
|
|
62
|
+
|
|
63
|
+
Use `auditauth.fetch()` instead of `window.fetch()` for authenticated requests.
|
|
64
|
+
The SDK attaches the access token and automatically attempts refresh when a
|
|
65
|
+
request returns `401`.
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const response = await auditauth.fetch('https://api.example.com/private')
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Use session actions
|
|
72
|
+
|
|
73
|
+
You can trigger user session actions from your UI.
|
|
74
|
+
|
|
75
|
+
- `auditauth.login()` starts the login flow
|
|
76
|
+
- `auditauth.logout()` revokes and clears the session
|
|
77
|
+
- `auditauth.goToPortal()` redirects to the AuditAuth portal
|
|
78
|
+
|
|
79
|
+
## API reference
|
|
80
|
+
|
|
81
|
+
These methods are available in `AuditAuthWeb`.
|
|
82
|
+
|
|
83
|
+
- `handleRedirect(): Promise<void | null>`
|
|
84
|
+
- `isAuthenticated(): boolean`
|
|
85
|
+
- `getSessionUser(): SessionUser | null`
|
|
86
|
+
- `login(): Promise<void>`
|
|
87
|
+
- `logout(): Promise<void>`
|
|
88
|
+
- `goToPortal(): Promise<void>`
|
|
89
|
+
- `fetch(input, init?): Promise<Response>`
|
|
90
|
+
- `trackNavigationPath(path: string): void`
|
|
91
|
+
- `initNavigationTracking(): void`
|
|
92
|
+
|
|
93
|
+
## Compatibility
|
|
94
|
+
|
|
95
|
+
This package requires Node.js `>=18.18.0` for tooling and build environments.
|
|
96
|
+
|
|
97
|
+
## Resources
|
|
98
|
+
|
|
99
|
+
- Repository: https://github.com/nimibyte/auditauth-sdk
|
|
100
|
+
- Documentation: https://docs.auditauth.com
|
|
101
|
+
|
|
102
|
+
## Example
|
|
103
|
+
|
|
104
|
+
See the complete integration in `examples/vanilla/src`.
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './sdk';
|
package/dist/sdk.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { AuditAuthConfig } from "@auditauth/core";
|
|
2
|
+
import { StorageAdapter } from "./types";
|
|
3
|
+
declare class AuditAuthWeb {
|
|
4
|
+
private config;
|
|
5
|
+
private storage;
|
|
6
|
+
constructor(config: AuditAuthConfig, storage: StorageAdapter);
|
|
7
|
+
private getWithExpiry;
|
|
8
|
+
private setWithExpiry;
|
|
9
|
+
private getSessionId;
|
|
10
|
+
private pushMetric;
|
|
11
|
+
trackNavigationPath(path: string): void;
|
|
12
|
+
initNavigationTracking(): void;
|
|
13
|
+
fetch(input: RequestInfo, init?: RequestInit): Promise<Response>;
|
|
14
|
+
isAuthenticated(): boolean;
|
|
15
|
+
getSessionUser(): any;
|
|
16
|
+
goToPortal(): Promise<void>;
|
|
17
|
+
logout(): Promise<void>;
|
|
18
|
+
login(): Promise<void>;
|
|
19
|
+
handleRedirect(): Promise<null | undefined>;
|
|
20
|
+
}
|
|
21
|
+
export { AuditAuthWeb };
|
package/dist/sdk.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { authorizeCode, buildAuthUrl, buildPortalUrl, CORE_SETTINGS, refreshTokens, revokeSession } from "@auditauth/core";
|
|
2
|
+
class AuditAuthWeb {
|
|
3
|
+
constructor(config, storage) {
|
|
4
|
+
this.config = config;
|
|
5
|
+
this.storage = storage;
|
|
6
|
+
}
|
|
7
|
+
getWithExpiry(key) {
|
|
8
|
+
const itemStr = this.storage.get(key);
|
|
9
|
+
if (!itemStr)
|
|
10
|
+
return null;
|
|
11
|
+
const item = JSON.parse(itemStr);
|
|
12
|
+
if (Date.now() > item.expiresAt) {
|
|
13
|
+
this.storage.remove(key);
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
return item.value;
|
|
17
|
+
}
|
|
18
|
+
setWithExpiry(key, value, ttlSeconds) {
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
const item = {
|
|
21
|
+
value,
|
|
22
|
+
expiresAt: now + ttlSeconds * 1000,
|
|
23
|
+
};
|
|
24
|
+
this.storage.set(key, JSON.stringify(item));
|
|
25
|
+
}
|
|
26
|
+
getSessionId() {
|
|
27
|
+
let session_id = this.getWithExpiry(CORE_SETTINGS.storage_keys.session_id);
|
|
28
|
+
if (!session_id) {
|
|
29
|
+
session_id = crypto.randomUUID();
|
|
30
|
+
this.setWithExpiry(CORE_SETTINGS.storage_keys.session_id, session_id, 60 * 5);
|
|
31
|
+
}
|
|
32
|
+
return session_id;
|
|
33
|
+
}
|
|
34
|
+
pushMetric(metric) {
|
|
35
|
+
const session_id = this.getSessionId();
|
|
36
|
+
const body = JSON.stringify({
|
|
37
|
+
appId: this.config.appId,
|
|
38
|
+
apiKey: this.config.apiKey,
|
|
39
|
+
session_id,
|
|
40
|
+
...metric,
|
|
41
|
+
});
|
|
42
|
+
const url = `${CORE_SETTINGS.domains.api}/metrics`;
|
|
43
|
+
if (navigator.sendBeacon) {
|
|
44
|
+
const blob = new Blob([body], { type: 'application/json' });
|
|
45
|
+
navigator.sendBeacon(url, blob);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
fetch(url, {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
headers: { 'Content-Type': 'application/json' },
|
|
51
|
+
body,
|
|
52
|
+
keepalive: true,
|
|
53
|
+
}).catch(() => { });
|
|
54
|
+
}
|
|
55
|
+
trackNavigationPath(path) {
|
|
56
|
+
this.pushMetric({
|
|
57
|
+
event_type: 'navigation',
|
|
58
|
+
runtime: 'browser',
|
|
59
|
+
target: {
|
|
60
|
+
type: 'page',
|
|
61
|
+
path: path,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
initNavigationTracking() {
|
|
66
|
+
const track = () => {
|
|
67
|
+
this.pushMetric({
|
|
68
|
+
event_type: 'navigation',
|
|
69
|
+
runtime: 'browser',
|
|
70
|
+
target: {
|
|
71
|
+
type: 'page',
|
|
72
|
+
path: window.location.pathname,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
window.addEventListener('popstate', track);
|
|
77
|
+
const originalPushState = history.pushState;
|
|
78
|
+
history.pushState = (...args) => {
|
|
79
|
+
originalPushState.apply(history, args);
|
|
80
|
+
track();
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async fetch(input, init = {}) {
|
|
84
|
+
const start = performance.now();
|
|
85
|
+
let access = this.getWithExpiry(CORE_SETTINGS.storage_keys.access);
|
|
86
|
+
const doFetch = (token) => fetch(input, {
|
|
87
|
+
...init,
|
|
88
|
+
credentials: 'include',
|
|
89
|
+
headers: {
|
|
90
|
+
...init.headers,
|
|
91
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
let res = await doFetch(access || undefined);
|
|
95
|
+
if (res.status === 401) {
|
|
96
|
+
const refreshData = await refreshTokens({ client_type: 'browser' });
|
|
97
|
+
if (!refreshData) {
|
|
98
|
+
await this.logout();
|
|
99
|
+
return res;
|
|
100
|
+
}
|
|
101
|
+
this.setWithExpiry(CORE_SETTINGS.storage_keys.access, refreshData.access_token, refreshData.access_expires_seconds);
|
|
102
|
+
res = await doFetch(refreshData.access_token);
|
|
103
|
+
}
|
|
104
|
+
this.pushMetric({
|
|
105
|
+
event_type: 'request',
|
|
106
|
+
runtime: 'browser',
|
|
107
|
+
target: {
|
|
108
|
+
type: 'api',
|
|
109
|
+
method: init.method || 'GET',
|
|
110
|
+
path: typeof input === 'string'
|
|
111
|
+
? input
|
|
112
|
+
: input instanceof Request
|
|
113
|
+
? input.url
|
|
114
|
+
: '',
|
|
115
|
+
status: res.status,
|
|
116
|
+
duration_ms: Math.round(performance.now() - start),
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
return res;
|
|
120
|
+
}
|
|
121
|
+
isAuthenticated() {
|
|
122
|
+
return !!this.getWithExpiry(CORE_SETTINGS.storage_keys.session);
|
|
123
|
+
}
|
|
124
|
+
getSessionUser() {
|
|
125
|
+
const value = this.getWithExpiry(CORE_SETTINGS.storage_keys.session);
|
|
126
|
+
return value ? value?.user : null;
|
|
127
|
+
}
|
|
128
|
+
async goToPortal() {
|
|
129
|
+
const access_token = this.getWithExpiry(CORE_SETTINGS.storage_keys.access);
|
|
130
|
+
if (!access_token)
|
|
131
|
+
return this.login();
|
|
132
|
+
const url = await buildPortalUrl({ access_token, redirectUrl: this.config.redirectUrl });
|
|
133
|
+
window.location.href = url.href;
|
|
134
|
+
}
|
|
135
|
+
async logout() {
|
|
136
|
+
const access_token = this.getWithExpiry(CORE_SETTINGS.storage_keys.access);
|
|
137
|
+
await revokeSession({ access_token }).catch(() => { });
|
|
138
|
+
this.storage.remove(CORE_SETTINGS.storage_keys.access);
|
|
139
|
+
this.storage.remove(CORE_SETTINGS.storage_keys.session);
|
|
140
|
+
window.location.reload();
|
|
141
|
+
}
|
|
142
|
+
async login() {
|
|
143
|
+
const url = await buildAuthUrl({
|
|
144
|
+
apiKey: this.config.apiKey,
|
|
145
|
+
redirectUrl: this.config.redirectUrl,
|
|
146
|
+
});
|
|
147
|
+
window.location.href = url.href;
|
|
148
|
+
}
|
|
149
|
+
async handleRedirect() {
|
|
150
|
+
const url = new URL(window.location.href);
|
|
151
|
+
const code = url.searchParams.get('code');
|
|
152
|
+
try {
|
|
153
|
+
const { data } = await authorizeCode({ code, client_type: 'browser' });
|
|
154
|
+
this.setWithExpiry(CORE_SETTINGS.storage_keys.access, data.access_token, data.access_expires_seconds);
|
|
155
|
+
this.setWithExpiry(CORE_SETTINGS.storage_keys.session, { user: data.user }, data.access_expires_seconds);
|
|
156
|
+
url.searchParams.delete('code');
|
|
157
|
+
window.history.replaceState({}, document.title, url.pathname + url.search);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
export { AuditAuthWeb };
|
package/dist/types.d.ts
ADDED
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@auditauth/web",
|
|
3
|
+
"version": "0.2.0-beta.1",
|
|
4
|
+
"description": "AuditAuth Web SDK (framework-agnostic)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Nimibyte",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=18.18.0"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/nimibyte/auditauth-sdk.git"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://docs.auditauth.com",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/nimibyte/auditauth-sdk/issues"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"authentication",
|
|
20
|
+
"auth",
|
|
21
|
+
"oauth",
|
|
22
|
+
"identity",
|
|
23
|
+
"jwt",
|
|
24
|
+
"security",
|
|
25
|
+
"auditauth"
|
|
26
|
+
],
|
|
27
|
+
"module": "dist/index.js",
|
|
28
|
+
"type": "module",
|
|
29
|
+
"main": "dist/index.js",
|
|
30
|
+
"types": "dist/index.d.ts",
|
|
31
|
+
"files": [
|
|
32
|
+
"dist"
|
|
33
|
+
],
|
|
34
|
+
"sideEffects": false,
|
|
35
|
+
"exports": {
|
|
36
|
+
".": {
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"default": "./dist/index.js"
|
|
39
|
+
},
|
|
40
|
+
"./package.json": "./package.json"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsc -p tsconfig.build.json",
|
|
44
|
+
"dev": "tsc -p tsconfig.build.json --watch",
|
|
45
|
+
"clean": "rm -rf dist"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@auditauth/core": "^0.2.0-beta.1"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"typescript": "^5.3.3"
|
|
52
|
+
}
|
|
53
|
+
}
|