authfyio-vue 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 +21 -0
- package/dist/index.d.ts +96 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +169 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# authfyio-vue
|
|
2
|
+
|
|
3
|
+
Vue 3 composables for Authfyio — useAuth, useUser, useSession, and conditional render helpers.
|
|
4
|
+
|
|
5
|
+
> Part of [Authfyio](https://authfyio.com) — a self-hostable authentication platform.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install authfyio-vue
|
|
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
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { type Ref } from 'vue';
|
|
2
|
+
/**
|
|
3
|
+
* Vue 3 composables for Authfyio. Provide the base URL near the root of
|
|
4
|
+
* your app with `provideAuthfyio` and then call `useAuth` / `useUser` /
|
|
5
|
+
* `useSession` in any component. Auto-refresh runs for as long as the
|
|
6
|
+
* provider is mounted.
|
|
7
|
+
*/
|
|
8
|
+
export type SessionClaims = {
|
|
9
|
+
sid: string;
|
|
10
|
+
sub: string;
|
|
11
|
+
env: string;
|
|
12
|
+
org?: string;
|
|
13
|
+
org_role?: string;
|
|
14
|
+
iat?: number;
|
|
15
|
+
exp?: number;
|
|
16
|
+
};
|
|
17
|
+
type SessionState = {
|
|
18
|
+
status: 'signed_out';
|
|
19
|
+
} | {
|
|
20
|
+
status: 'signed_in';
|
|
21
|
+
jwt: string;
|
|
22
|
+
claims: SessionClaims;
|
|
23
|
+
};
|
|
24
|
+
type KolayCtx = {
|
|
25
|
+
baseUrl: string;
|
|
26
|
+
session: Ref<SessionState>;
|
|
27
|
+
refresh: () => Promise<void>;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Browser-side default. The Vue SDK assumes you've mounted a same-origin
|
|
31
|
+
* proxy at `/api/af` (use `authfyio-nextjs/proxy` for Next, or write
|
|
32
|
+
* an equivalent for your framework). Override `baseUrl` for self-hosted
|
|
33
|
+
* setups or non-Next backends.
|
|
34
|
+
*/
|
|
35
|
+
export declare const DEFAULT_BASE_URL = "/api/af";
|
|
36
|
+
export type ProvideAuthfyioOptions = {
|
|
37
|
+
baseUrl?: string;
|
|
38
|
+
autoRefresh?: boolean;
|
|
39
|
+
refreshSkewSeconds?: number;
|
|
40
|
+
};
|
|
41
|
+
export declare function provideAuthfyio(opts?: ProvideAuthfyioOptions): KolayCtx;
|
|
42
|
+
export declare function useAuthfyio(): KolayCtx;
|
|
43
|
+
export declare function useSession(): Ref<SessionState, SessionState>;
|
|
44
|
+
type HasCheck = {
|
|
45
|
+
role?: string;
|
|
46
|
+
permission?: string;
|
|
47
|
+
plan?: string;
|
|
48
|
+
feature?: string;
|
|
49
|
+
};
|
|
50
|
+
export declare function useAuth(): import("vue").ComputedRef<{
|
|
51
|
+
isSignedIn: false;
|
|
52
|
+
userId: null;
|
|
53
|
+
sessionId: null;
|
|
54
|
+
orgId: null;
|
|
55
|
+
orgRole: null;
|
|
56
|
+
environmentId: null;
|
|
57
|
+
getToken: () => string | null;
|
|
58
|
+
has: (_check: HasCheck) => boolean;
|
|
59
|
+
} | {
|
|
60
|
+
isSignedIn: true;
|
|
61
|
+
userId: string;
|
|
62
|
+
sessionId: string;
|
|
63
|
+
orgId: string | null;
|
|
64
|
+
orgRole: string | null;
|
|
65
|
+
environmentId: string;
|
|
66
|
+
getToken: () => string | null;
|
|
67
|
+
has: (check: HasCheck) => boolean;
|
|
68
|
+
}>;
|
|
69
|
+
export type UserResource = {
|
|
70
|
+
id: string;
|
|
71
|
+
email: string | null;
|
|
72
|
+
username: string | null;
|
|
73
|
+
firstName: string | null;
|
|
74
|
+
lastName: string | null;
|
|
75
|
+
publicMetadata: Record<string, unknown>;
|
|
76
|
+
};
|
|
77
|
+
export declare function useUser(): {
|
|
78
|
+
user: Ref<{
|
|
79
|
+
id: string;
|
|
80
|
+
email: string | null;
|
|
81
|
+
username: string | null;
|
|
82
|
+
firstName: string | null;
|
|
83
|
+
lastName: string | null;
|
|
84
|
+
publicMetadata: Record<string, unknown>;
|
|
85
|
+
} | null, UserResource | {
|
|
86
|
+
id: string;
|
|
87
|
+
email: string | null;
|
|
88
|
+
username: string | null;
|
|
89
|
+
firstName: string | null;
|
|
90
|
+
lastName: string | null;
|
|
91
|
+
publicMetadata: Record<string, unknown>;
|
|
92
|
+
} | null>;
|
|
93
|
+
isLoaded: Ref<boolean, boolean>;
|
|
94
|
+
};
|
|
95
|
+
export {};
|
|
96
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiF,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AAE9G;;;;;GAKG;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,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,KAAK,YAAY,GACb;IAAE,MAAM,EAAE,YAAY,CAAA;CAAE,GACxB;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,aAAa,CAAA;CAAE,CAAC;AAEhE,KAAK,QAAQ,GAAG;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC;IAC3B,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B,CAAC;AAIF;;;;;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,wBAAgB,eAAe,CAAC,IAAI,GAAE,sBAA2B,GAAG,QAAQ,CA6C3E;AAED,wBAAgB,WAAW,IAAI,QAAQ,CAItC;AAED,wBAAgB,UAAU,oCAEzB;AAED,KAAK,QAAQ,GAAG;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,wBAAgB,OAAO;;;;;;;oBAWS,MAAM,GAAG,IAAI;kBACvB,QAAQ,KAAG,OAAO;;;;;;;;oBAYX,MAAM,GAAG,IAAI;iBACvB,QAAQ,KAAG,OAAO;GAYpC;AAED,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC,CAAC;AAEF,wBAAgB,OAAO;;YARjB,MAAM;eACH,MAAM,GAAG,IAAI;kBACV,MAAM,GAAG,IAAI;mBACZ,MAAM,GAAG,IAAI;kBACd,MAAM,GAAG,IAAI;wBACP,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;YALnC,MAAM;eACH,MAAM,GAAG,IAAI;kBACV,MAAM,GAAG,IAAI;mBACZ,MAAM,GAAG,IAAI;kBACd,MAAM,GAAG,IAAI;wBACP,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;EAiCxC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { inject, provide, ref, computed, onBeforeUnmount, onMounted } from 'vue';
|
|
2
|
+
const KolayKey = Symbol('authfyio-vue');
|
|
3
|
+
/**
|
|
4
|
+
* Browser-side default. The Vue SDK assumes you've mounted a same-origin
|
|
5
|
+
* proxy at `/api/af` (use `authfyio-nextjs/proxy` for Next, or write
|
|
6
|
+
* an equivalent for your framework). Override `baseUrl` for self-hosted
|
|
7
|
+
* setups or non-Next backends.
|
|
8
|
+
*/
|
|
9
|
+
export const DEFAULT_BASE_URL = '/api/af';
|
|
10
|
+
export function provideAuthfyio(opts = {}) {
|
|
11
|
+
const baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
|
|
12
|
+
const session = ref(loadInitial());
|
|
13
|
+
async function refresh() {
|
|
14
|
+
const res = await fetch(`${baseUrl}/v1/auth/sessions/refresh`, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
credentials: 'include',
|
|
17
|
+
});
|
|
18
|
+
if (!res.ok) {
|
|
19
|
+
session.value = { status: 'signed_out' };
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const next = loadFromCookie();
|
|
23
|
+
session.value = next ?? { status: 'signed_out' };
|
|
24
|
+
}
|
|
25
|
+
let timer = null;
|
|
26
|
+
function schedule() {
|
|
27
|
+
if (timer)
|
|
28
|
+
clearTimeout(timer);
|
|
29
|
+
if (opts.autoRefresh === false)
|
|
30
|
+
return;
|
|
31
|
+
if (session.value.status !== 'signed_in')
|
|
32
|
+
return;
|
|
33
|
+
const exp = session.value.claims.exp;
|
|
34
|
+
if (!exp)
|
|
35
|
+
return;
|
|
36
|
+
const skew = opts.refreshSkewSeconds ?? 15;
|
|
37
|
+
const msUntil = Math.max(0, exp * 1000 - Date.now() - skew * 1000);
|
|
38
|
+
timer = setTimeout(() => {
|
|
39
|
+
refresh().catch(() => { });
|
|
40
|
+
}, msUntil);
|
|
41
|
+
}
|
|
42
|
+
onMounted(() => {
|
|
43
|
+
const fresh = loadFromCookie();
|
|
44
|
+
if (fresh)
|
|
45
|
+
session.value = fresh;
|
|
46
|
+
schedule();
|
|
47
|
+
});
|
|
48
|
+
onBeforeUnmount(() => {
|
|
49
|
+
if (timer)
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
});
|
|
52
|
+
const ctx = { baseUrl, session, refresh };
|
|
53
|
+
provide(KolayKey, ctx);
|
|
54
|
+
return ctx;
|
|
55
|
+
}
|
|
56
|
+
export function useAuthfyio() {
|
|
57
|
+
const ctx = inject(KolayKey);
|
|
58
|
+
if (!ctx)
|
|
59
|
+
throw new Error('provideAuthfyio() must be called in an ancestor component.');
|
|
60
|
+
return ctx;
|
|
61
|
+
}
|
|
62
|
+
export function useSession() {
|
|
63
|
+
return useAuthfyio().session;
|
|
64
|
+
}
|
|
65
|
+
export function useAuth() {
|
|
66
|
+
const session = useSession();
|
|
67
|
+
return computed(() => {
|
|
68
|
+
if (session.value.status !== 'signed_in') {
|
|
69
|
+
return {
|
|
70
|
+
isSignedIn: false,
|
|
71
|
+
userId: null,
|
|
72
|
+
sessionId: null,
|
|
73
|
+
orgId: null,
|
|
74
|
+
orgRole: null,
|
|
75
|
+
environmentId: null,
|
|
76
|
+
getToken: () => null,
|
|
77
|
+
has: (_check) => false,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const { claims, jwt } = session.value;
|
|
81
|
+
const orgPerms = Array.isArray(claims.org_perms) ? claims.org_perms : [];
|
|
82
|
+
return {
|
|
83
|
+
isSignedIn: true,
|
|
84
|
+
userId: claims.sub,
|
|
85
|
+
sessionId: claims.sid,
|
|
86
|
+
orgId: claims.org ?? null,
|
|
87
|
+
orgRole: claims.org_role ?? null,
|
|
88
|
+
environmentId: claims.env,
|
|
89
|
+
getToken: () => jwt,
|
|
90
|
+
has: (check) => {
|
|
91
|
+
if (check.role && claims.org_role !== check.role)
|
|
92
|
+
return false;
|
|
93
|
+
if (check.permission) {
|
|
94
|
+
if (!claims.org)
|
|
95
|
+
return false;
|
|
96
|
+
if (!orgPerms.includes(check.permission))
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
// plan / feature live in the billing surface — wire those up
|
|
100
|
+
// when authfyio-vue grows useBilling().
|
|
101
|
+
return true;
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
export function useUser() {
|
|
107
|
+
const { baseUrl } = useAuthfyio();
|
|
108
|
+
const session = useSession();
|
|
109
|
+
const user = ref(null);
|
|
110
|
+
const isLoaded = ref(false);
|
|
111
|
+
async function load() {
|
|
112
|
+
if (session.value.status !== 'signed_in') {
|
|
113
|
+
user.value = null;
|
|
114
|
+
isLoaded.value = true;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
const res = await fetch(`${baseUrl}/v1/auth/sessions/current`, { credentials: 'include' });
|
|
119
|
+
if (!res.ok) {
|
|
120
|
+
user.value = null;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const body = (await res.json());
|
|
124
|
+
user.value = body.user ?? null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
user.value = null;
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
isLoaded.value = true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
onMounted(load);
|
|
135
|
+
return { user, isLoaded };
|
|
136
|
+
}
|
|
137
|
+
/* ──────────────── Internals ──────────────── */
|
|
138
|
+
function loadInitial() {
|
|
139
|
+
return loadFromCookie() ?? { status: 'signed_out' };
|
|
140
|
+
}
|
|
141
|
+
function loadFromCookie() {
|
|
142
|
+
if (typeof document === 'undefined')
|
|
143
|
+
return null;
|
|
144
|
+
const pairs = document.cookie.split(';');
|
|
145
|
+
for (const p of pairs) {
|
|
146
|
+
const eq = p.indexOf('=');
|
|
147
|
+
if (eq < 0)
|
|
148
|
+
continue;
|
|
149
|
+
const name = p.slice(0, eq).trim();
|
|
150
|
+
if (name !== '__session')
|
|
151
|
+
continue;
|
|
152
|
+
const value = decodeURIComponent(p.slice(eq + 1).trim());
|
|
153
|
+
try {
|
|
154
|
+
const claims = decodeJwt(value);
|
|
155
|
+
return { status: 'signed_in', jwt: value, claims };
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
function decodeJwt(token) {
|
|
164
|
+
const [, payload] = token.split('.');
|
|
165
|
+
const b64 = payload.replace(/-/g, '+').replace(/_/g, '/');
|
|
166
|
+
const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4);
|
|
167
|
+
const json = atob(padded);
|
|
168
|
+
return JSON.parse(json);
|
|
169
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "authfyio-vue",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Vue 3 composables for Authfyio — useAuth, useUser, useSession, and conditional render helpers.",
|
|
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
|
+
"peerDependencies": {
|
|
25
|
+
"vue": ">=3"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"authfyio",
|
|
29
|
+
"auth",
|
|
30
|
+
"vue",
|
|
31
|
+
"composables"
|
|
32
|
+
],
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://authfyio.com/docs",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/authfyio/authfyio.git"
|
|
40
|
+
}
|
|
41
|
+
}
|