authfyio-javascript 0.2.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 ADDED
@@ -0,0 +1,21 @@
1
+ # authfyio-javascript
2
+
3
+ Framework-free JavaScript SDK for Authfyio — browser sessions, auth state subscription, and admin calls.
4
+
5
+ > Part of [Authfyio](https://authfyio.com) — a self-hostable authentication platform.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install authfyio-javascript
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ See the full guide at **https://authfyio.com/docs**.
16
+
17
+ Point the SDK at your Authfyio instance via the same-origin proxy (`/api/af`) or set `AF_API_BASE_URL` for server-side calls.
18
+
19
+ ## License
20
+
21
+ MIT
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Framework-free JavaScript SDK — works with Vite, Webpack, Rollup, ESM
3
+ * CDN, or plain `<script type="module">`.
4
+ *
5
+ * import { createAuthfyio } from 'authfyio-javascript';
6
+ * const kolay = createAuthfyio({ baseUrl: 'https://fapi.example.com' });
7
+ * kolay.onAuthChange((auth) => {
8
+ * if (auth.isSignedIn) document.body.dataset.user = auth.userId!;
9
+ * });
10
+ * await kolay.signInEmailPassword('a@b.com', '…');
11
+ *
12
+ * Sessions are driven by the two-cookie Authfyio model — this SDK reads
13
+ * `__session` on every tick to stay in sync and calls `/sessions/refresh`
14
+ * before the JWT expires.
15
+ */
16
+ export type SessionClaims = {
17
+ sid: string;
18
+ sub: string;
19
+ env: string;
20
+ org?: string;
21
+ org_role?: string;
22
+ /** Permission keys held in the active org (e.g. 'org:sys_billing:manage'). */
23
+ org_perms?: string[];
24
+ iat?: number;
25
+ exp?: number;
26
+ };
27
+ export type HasCheck = {
28
+ role?: string;
29
+ permission?: string;
30
+ };
31
+ export type AuthState = {
32
+ isSignedIn: boolean;
33
+ userId: string | null;
34
+ sessionId: string | null;
35
+ orgId: string | null;
36
+ orgRole: string | null;
37
+ claims: SessionClaims | null;
38
+ jwt: string | null;
39
+ };
40
+ /**
41
+ * Browser-side default. Assumes a same-origin proxy mounted at `/api/af`
42
+ * (e.g. `authfyio-nextjs/proxy` for Next.js apps). Override `baseUrl`
43
+ * if you mount the proxy elsewhere or call api.authfyio.com directly
44
+ * from a same-eTLD+1 host.
45
+ */
46
+ export declare const DEFAULT_BASE_URL = "/api/af";
47
+ export type AuthfyioBrowserOptions = {
48
+ baseUrl?: string;
49
+ autoRefresh?: boolean;
50
+ refreshSkewSeconds?: number;
51
+ };
52
+ export type AuthfyioBrowser = {
53
+ state(): AuthState;
54
+ onAuthChange(listener: (state: AuthState) => void): () => void;
55
+ refresh(): Promise<AuthState>;
56
+ signInEmailPassword(email: string, password: string): Promise<AuthState>;
57
+ signUpEmailPassword(email: string, password: string): Promise<AuthState>;
58
+ signInMagicLink(email: string): Promise<void>;
59
+ signOut(): Promise<void>;
60
+ oauthRedirectUrl(provider: 'google' | 'github'): string;
61
+ /** Raw JWT for attaching to your own API requests. */
62
+ getToken(): string | null;
63
+ /**
64
+ * Synchronous gating helper. Reads the JWT's `org_role` / `org_perms`
65
+ * claims so it works without a network round-trip. Returns false when
66
+ * the user has no active org (for permission checks) or no matching
67
+ * role.
68
+ */
69
+ has(check: HasCheck): boolean;
70
+ };
71
+ export declare function createAuthfyio(opts?: AuthfyioBrowserOptions): AuthfyioBrowser;
72
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IAC7B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAE1C,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,IAAI,SAAS,CAAC;IACnB,YAAY,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAC/D,OAAO,IAAI,OAAO,CAAC,SAAS,CAAC,CAAC;IAC9B,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACzE,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IACzE,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;IACxD,sDAAsD;IACtD,QAAQ,IAAI,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;;OAKG;IACH,GAAG,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC;CAC/B,CAAC;AAEF,wBAAgB,cAAc,CAAC,IAAI,GAAE,sBAA2B,GAAG,eAAe,CAmIjF"}
package/dist/index.js ADDED
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Framework-free JavaScript SDK — works with Vite, Webpack, Rollup, ESM
3
+ * CDN, or plain `<script type="module">`.
4
+ *
5
+ * import { createAuthfyio } from 'authfyio-javascript';
6
+ * const kolay = createAuthfyio({ baseUrl: 'https://fapi.example.com' });
7
+ * kolay.onAuthChange((auth) => {
8
+ * if (auth.isSignedIn) document.body.dataset.user = auth.userId!;
9
+ * });
10
+ * await kolay.signInEmailPassword('a@b.com', '…');
11
+ *
12
+ * Sessions are driven by the two-cookie Authfyio model — this SDK reads
13
+ * `__session` on every tick to stay in sync and calls `/sessions/refresh`
14
+ * before the JWT expires.
15
+ */
16
+ /**
17
+ * Browser-side default. Assumes a same-origin proxy mounted at `/api/af`
18
+ * (e.g. `authfyio-nextjs/proxy` for Next.js apps). Override `baseUrl`
19
+ * if you mount the proxy elsewhere or call api.authfyio.com directly
20
+ * from a same-eTLD+1 host.
21
+ */
22
+ export const DEFAULT_BASE_URL = '/api/af';
23
+ export function createAuthfyio(opts = {}) {
24
+ const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
25
+ const listeners = new Set();
26
+ let current = readState();
27
+ let refreshTimer = null;
28
+ function readState() {
29
+ const jwt = readSessionCookie();
30
+ if (!jwt)
31
+ return empty();
32
+ try {
33
+ const claims = decodeJwt(jwt);
34
+ return {
35
+ isSignedIn: true,
36
+ userId: claims.sub ?? null,
37
+ sessionId: claims.sid ?? null,
38
+ orgId: claims.org ?? null,
39
+ orgRole: claims.org_role ?? null,
40
+ claims,
41
+ jwt,
42
+ };
43
+ }
44
+ catch {
45
+ return empty();
46
+ }
47
+ }
48
+ function emit() {
49
+ const next = readState();
50
+ if (!shallowEqual(next, current)) {
51
+ current = next;
52
+ for (const fn of listeners)
53
+ fn(current);
54
+ }
55
+ else {
56
+ current = next;
57
+ }
58
+ }
59
+ function scheduleRefresh() {
60
+ if (refreshTimer)
61
+ clearTimeout(refreshTimer);
62
+ if (opts.autoRefresh === false)
63
+ return;
64
+ if (!current.isSignedIn || !current.claims?.exp)
65
+ return;
66
+ const skew = opts.refreshSkewSeconds ?? 15;
67
+ const msUntil = Math.max(0, current.claims.exp * 1000 - Date.now() - skew * 1000);
68
+ refreshTimer = setTimeout(async () => {
69
+ try {
70
+ await api.refresh();
71
+ }
72
+ catch { }
73
+ }, msUntil);
74
+ }
75
+ const api = {
76
+ state: () => current,
77
+ onAuthChange(fn) {
78
+ listeners.add(fn);
79
+ fn(current);
80
+ return () => listeners.delete(fn);
81
+ },
82
+ async refresh() {
83
+ await fetch(`${baseUrl}/v1/auth/sessions/refresh`, {
84
+ method: 'POST',
85
+ credentials: 'include',
86
+ });
87
+ emit();
88
+ scheduleRefresh();
89
+ return current;
90
+ },
91
+ async signInEmailPassword(email, password) {
92
+ const res = await fetch(`${baseUrl}/v1/auth/sign-in/email-password`, {
93
+ method: 'POST',
94
+ credentials: 'include',
95
+ headers: { 'content-type': 'application/json' },
96
+ body: JSON.stringify({ email, password }),
97
+ });
98
+ if (!res.ok)
99
+ throw new Error('invalid_credentials');
100
+ emit();
101
+ scheduleRefresh();
102
+ return current;
103
+ },
104
+ async signUpEmailPassword(email, password) {
105
+ const res = await fetch(`${baseUrl}/v1/auth/sign-up/email-password`, {
106
+ method: 'POST',
107
+ credentials: 'include',
108
+ headers: { 'content-type': 'application/json' },
109
+ body: JSON.stringify({ email, password }),
110
+ });
111
+ if (!res.ok) {
112
+ const body = (await res.json().catch(() => null));
113
+ throw new Error(body?.message ?? 'signup_failed');
114
+ }
115
+ emit();
116
+ scheduleRefresh();
117
+ return current;
118
+ },
119
+ async signInMagicLink(email) {
120
+ await fetch(`${baseUrl}/v1/auth/sign-in/magic-link`, {
121
+ method: 'POST',
122
+ credentials: 'include',
123
+ headers: { 'content-type': 'application/json' },
124
+ body: JSON.stringify({ email }),
125
+ });
126
+ },
127
+ async signOut() {
128
+ await fetch(`${baseUrl}/v1/auth/sign-out`, {
129
+ method: 'POST',
130
+ credentials: 'include',
131
+ });
132
+ emit();
133
+ if (refreshTimer)
134
+ clearTimeout(refreshTimer);
135
+ },
136
+ oauthRedirectUrl(provider) {
137
+ return `${baseUrl}/v1/auth/oauth/${provider}/authorize`;
138
+ },
139
+ getToken() {
140
+ return current.jwt;
141
+ },
142
+ has(check) {
143
+ if (!current.isSignedIn || !current.claims)
144
+ return false;
145
+ if (check.role && current.claims.org_role !== check.role)
146
+ return false;
147
+ if (check.permission) {
148
+ if (!current.claims.org)
149
+ return false;
150
+ const perms = Array.isArray(current.claims.org_perms) ? current.claims.org_perms : [];
151
+ if (!perms.includes(check.permission))
152
+ return false;
153
+ }
154
+ return true;
155
+ },
156
+ };
157
+ scheduleRefresh();
158
+ // Cheap drift detection — re-check cookies every 30s in case another tab
159
+ // (or a direct server-side rotation) changed the JWT.
160
+ if (typeof window !== 'undefined') {
161
+ setInterval(emit, 30_000);
162
+ }
163
+ return api;
164
+ }
165
+ function empty() {
166
+ return {
167
+ isSignedIn: false,
168
+ userId: null,
169
+ sessionId: null,
170
+ orgId: null,
171
+ orgRole: null,
172
+ claims: null,
173
+ jwt: null,
174
+ };
175
+ }
176
+ function shallowEqual(a, b) {
177
+ return (a.isSignedIn === b.isSignedIn &&
178
+ a.userId === b.userId &&
179
+ a.sessionId === b.sessionId &&
180
+ a.orgId === b.orgId &&
181
+ a.orgRole === b.orgRole &&
182
+ a.jwt === b.jwt);
183
+ }
184
+ function readSessionCookie() {
185
+ if (typeof document === 'undefined')
186
+ return null;
187
+ for (const pair of document.cookie.split(';')) {
188
+ const eq = pair.indexOf('=');
189
+ if (eq <= 0)
190
+ continue;
191
+ const name = pair.slice(0, eq).trim();
192
+ if (name !== '__session')
193
+ continue;
194
+ return decodeURIComponent(pair.slice(eq + 1).trim());
195
+ }
196
+ return null;
197
+ }
198
+ function decodeJwt(token) {
199
+ const [, payload] = token.split('.');
200
+ const b64 = payload.replace(/-/g, '+').replace(/_/g, '/');
201
+ const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4);
202
+ const json = atob(padded);
203
+ return JSON.parse(json);
204
+ }
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "authfyio-javascript",
3
+ "version": "0.2.1",
4
+ "description": "Framework-free JavaScript SDK for Authfyio — browser sessions, auth state subscription, and admin calls.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.build.json",
21
+ "typecheck": "tsc -p tsconfig.build.json --noEmit",
22
+ "prepublishOnly": "npm run build"
23
+ },
24
+ "keywords": [
25
+ "authfyio",
26
+ "auth",
27
+ "javascript",
28
+ "browser",
29
+ "sdk"
30
+ ],
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "homepage": "https://authfyio.com/docs",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/authfyio/authfyio.git"
38
+ }
39
+ }