@autional/vue 0.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.
package/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # @authms/vue
2
+
3
+ AuthMS Vue SDK — `createAuthms()` plugin, `useAuthms()` composable, `v-auth` directive, and `authmsGuard` router guard.
4
+
5
+ ## What's Inside
6
+
7
+ - **`createAuthms(config)`** — Vue plugin that creates and initializes an `AuthMS` instance, injects via `provide`
8
+ - **`useAuthms()`** — composable returning `{ authms, user, isLoading, isAuthenticated, login, logout }`
9
+ - **`vAuth`** — directive for conditional rendering based on auth state
10
+ - **`authmsGuard`** — `beforeEach` navigation guard for vue-router
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm install @authms/core @authms/vue
16
+ ```
17
+
18
+ ## Quick Start
19
+
20
+ ```ts
21
+ // main.ts
22
+ import { createApp } from 'vue';
23
+ import { createAuthms } from '@authms/vue';
24
+ import App from './App.vue';
25
+
26
+ const app = createApp(App);
27
+ app.use(createAuthms({
28
+ appId: 'my-app',
29
+ issuer: 'https://auth.iam.tianv.com',
30
+ }));
31
+ app.mount('#app');
32
+ ```
33
+
34
+ ```vue
35
+ <!-- Dashboard.vue -->
36
+ <script setup lang="ts">
37
+ import { useAuthms } from '@authms/vue';
38
+
39
+ const { user, isLoading, login, logout } = useAuthms();
40
+ </script>
41
+
42
+ <template>
43
+ <div v-if="isLoading">Loading...</div>
44
+ <div v-else-if="!user">
45
+ <button @click="login({ email: 'a@b.com', password: 'x' })">Login</button>
46
+ </div>
47
+ <div v-else>
48
+ <h1>Welcome, {{ user.displayName }}</h1>
49
+ <button @click="logout">Logout</button>
50
+ </div>
51
+ </template>
52
+ ```
53
+
54
+ ## Project Setup
55
+
56
+ Copy [`examples/vue-authms.ts`](../../examples/vue-authms.ts) to `src/authms.ts`, edit `appId` and `issuer`. All your components import from `./authms`.
57
+
58
+ See the [root SDK README](../../README.md) for full documentation.
@@ -0,0 +1,43 @@
1
+ import { InjectionKey, Plugin, Ref, ComputedRef, Directive } from 'vue';
2
+ import { AuthMS, User, LoginRequest, AuthResult, OAuthOptions, RegisterRequest } from '@authms/core';
3
+
4
+ interface VueAuthmsConfig {
5
+ appId: string;
6
+ issuer: string;
7
+ apiUrl?: string;
8
+ storagePrefix?: string;
9
+ syncTabs?: boolean;
10
+ }
11
+ declare const AUTHMS_KEY: InjectionKey<AuthMS>;
12
+ declare function getAuthms(): AuthMS | null;
13
+ declare function createAuthms(config: VueAuthmsConfig): Plugin;
14
+
15
+ interface UseAuthmsReturn {
16
+ authms: AuthMS;
17
+ user: Ref<User | null>;
18
+ isLoading: Ref<boolean>;
19
+ isAuthenticated: ComputedRef<boolean>;
20
+ login: (credentials: LoginRequest) => Promise<AuthResult>;
21
+ loginWithOAuth: (options: OAuthOptions) => Promise<void>;
22
+ register: (data: RegisterRequest) => Promise<AuthResult>;
23
+ logout: () => Promise<void>;
24
+ getAccessToken: () => Promise<string | null>;
25
+ }
26
+ declare function useAuthms(): UseAuthmsReturn;
27
+
28
+ type AuthRoles = string | string[];
29
+ declare const vAuth: Directive<HTMLElement, AuthRoles>;
30
+
31
+ interface RouteLocation {
32
+ path: string;
33
+ fullPath: string;
34
+ query: Record<string, unknown>;
35
+ }
36
+ type GuardReturn = boolean | {
37
+ path: string;
38
+ query?: Record<string, string>;
39
+ };
40
+ type NavigationGuard = (to: RouteLocation, from: RouteLocation) => GuardReturn;
41
+ declare function authmsGuard(roles?: string[]): NavigationGuard;
42
+
43
+ export { AUTHMS_KEY, type UseAuthmsReturn, type VueAuthmsConfig, authmsGuard, createAuthms, getAuthms, useAuthms, vAuth };
@@ -0,0 +1,43 @@
1
+ import { InjectionKey, Plugin, Ref, ComputedRef, Directive } from 'vue';
2
+ import { AuthMS, User, LoginRequest, AuthResult, OAuthOptions, RegisterRequest } from '@authms/core';
3
+
4
+ interface VueAuthmsConfig {
5
+ appId: string;
6
+ issuer: string;
7
+ apiUrl?: string;
8
+ storagePrefix?: string;
9
+ syncTabs?: boolean;
10
+ }
11
+ declare const AUTHMS_KEY: InjectionKey<AuthMS>;
12
+ declare function getAuthms(): AuthMS | null;
13
+ declare function createAuthms(config: VueAuthmsConfig): Plugin;
14
+
15
+ interface UseAuthmsReturn {
16
+ authms: AuthMS;
17
+ user: Ref<User | null>;
18
+ isLoading: Ref<boolean>;
19
+ isAuthenticated: ComputedRef<boolean>;
20
+ login: (credentials: LoginRequest) => Promise<AuthResult>;
21
+ loginWithOAuth: (options: OAuthOptions) => Promise<void>;
22
+ register: (data: RegisterRequest) => Promise<AuthResult>;
23
+ logout: () => Promise<void>;
24
+ getAccessToken: () => Promise<string | null>;
25
+ }
26
+ declare function useAuthms(): UseAuthmsReturn;
27
+
28
+ type AuthRoles = string | string[];
29
+ declare const vAuth: Directive<HTMLElement, AuthRoles>;
30
+
31
+ interface RouteLocation {
32
+ path: string;
33
+ fullPath: string;
34
+ query: Record<string, unknown>;
35
+ }
36
+ type GuardReturn = boolean | {
37
+ path: string;
38
+ query?: Record<string, string>;
39
+ };
40
+ type NavigationGuard = (to: RouteLocation, from: RouteLocation) => GuardReturn;
41
+ declare function authmsGuard(roles?: string[]): NavigationGuard;
42
+
43
+ export { AUTHMS_KEY, type UseAuthmsReturn, type VueAuthmsConfig, authmsGuard, createAuthms, getAuthms, useAuthms, vAuth };
package/dist/index.js ADDED
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AUTHMS_KEY: () => AUTHMS_KEY,
24
+ authmsGuard: () => authmsGuard,
25
+ createAuthms: () => createAuthms,
26
+ getAuthms: () => getAuthms,
27
+ useAuthms: () => useAuthms,
28
+ vAuth: () => vAuth
29
+ });
30
+ module.exports = __toCommonJS(index_exports);
31
+
32
+ // src/createAuthms.ts
33
+ var import_vue = require("vue");
34
+ var import_core = require("@authms/core");
35
+ var AUTHMS_KEY = /* @__PURE__ */ Symbol("authms");
36
+ var _authms = null;
37
+ function getAuthms() {
38
+ return _authms;
39
+ }
40
+ function createAuthms(config) {
41
+ const coreConfig = {
42
+ appId: config.appId,
43
+ issuer: config.issuer,
44
+ apiUrl: config.apiUrl,
45
+ platform: import_core.browserPlatform,
46
+ storagePrefix: config.storagePrefix,
47
+ syncTabs: config.syncTabs
48
+ };
49
+ const authms = new import_core.AuthMS(coreConfig);
50
+ _authms = authms;
51
+ return {
52
+ install(app) {
53
+ app.provide(AUTHMS_KEY, authms);
54
+ authms.initialize().catch((err) => {
55
+ console.error("[AuthMS Vue] Initialization failed:", err);
56
+ });
57
+ }
58
+ };
59
+ }
60
+
61
+ // src/useAuthms.ts
62
+ var import_vue2 = require("vue");
63
+ var import_core2 = require("@authms/core");
64
+ function useAuthms() {
65
+ const authms = (0, import_vue2.inject)(AUTHMS_KEY);
66
+ if (!authms) {
67
+ throw new import_core2.AuthmsError(
68
+ "CONFIG_ERROR",
69
+ "useAuthms() must be used within a Vue app that has createAuthms() plugin installed",
70
+ 500
71
+ );
72
+ }
73
+ const user = (0, import_vue2.ref)(authms.user);
74
+ const isLoading = (0, import_vue2.ref)(!authms.isReady());
75
+ const isAuthenticated = (0, import_vue2.computed)(() => !!user.value);
76
+ let unsubReady;
77
+ let unsubUser;
78
+ (0, import_vue2.onMounted)(() => {
79
+ if (authms.isReady()) {
80
+ isLoading.value = false;
81
+ }
82
+ unsubReady = authms.on("READY", () => {
83
+ isLoading.value = false;
84
+ });
85
+ unsubUser = authms.on("USER_CHANGED", () => {
86
+ user.value = authms.user;
87
+ });
88
+ });
89
+ (0, import_vue2.onUnmounted)(() => {
90
+ unsubReady?.();
91
+ unsubUser?.();
92
+ });
93
+ return {
94
+ authms,
95
+ user,
96
+ isLoading,
97
+ isAuthenticated,
98
+ login: (credentials) => authms.login(credentials),
99
+ loginWithOAuth: (options) => authms.loginWithOAuth(options),
100
+ register: (data) => authms.register(data),
101
+ logout: () => authms.logout(),
102
+ getAccessToken: () => authms.getAccessToken()
103
+ };
104
+ }
105
+
106
+ // src/directive.ts
107
+ var import_vue3 = require("vue");
108
+ function hasRequiredRole(roles) {
109
+ const authms = getAuthms();
110
+ if (!authms?.isAuthenticated()) return false;
111
+ const user = authms.user;
112
+ if (!user) return false;
113
+ const userRole = user.role;
114
+ if (!userRole) return false;
115
+ const required = Array.isArray(roles) ? roles : [roles];
116
+ return required.length === 0 || required.includes(userRole);
117
+ }
118
+ function updateVisibility(el, binding) {
119
+ const visible = hasRequiredRole(binding.value);
120
+ if (visible) {
121
+ if (el.style.display === "none") {
122
+ el.style.display = el.__vAuthOrigDisplay ?? "";
123
+ }
124
+ } else {
125
+ if (el.style.display !== "none") {
126
+ el.__vAuthOrigDisplay = el.style.display;
127
+ el.style.display = "none";
128
+ }
129
+ }
130
+ }
131
+ var vAuth = {
132
+ mounted(el, binding) {
133
+ updateVisibility(el, binding);
134
+ },
135
+ updated(el, binding) {
136
+ if (binding.value !== binding.oldValue) {
137
+ updateVisibility(el, binding);
138
+ }
139
+ }
140
+ };
141
+
142
+ // src/guard.ts
143
+ function authmsGuard(roles) {
144
+ return (to) => {
145
+ const authms = getAuthms();
146
+ if (!authms || !authms.isReady()) {
147
+ return true;
148
+ }
149
+ if (!authms.isAuthenticated()) {
150
+ if (to.path === "/login") return true;
151
+ return { path: "/login", query: { redirect: to.fullPath } };
152
+ }
153
+ if (roles && roles.length > 0) {
154
+ const user = authms.user;
155
+ const userRole = user?.["role"];
156
+ if (!userRole || !roles.includes(userRole)) {
157
+ return { path: "/forbidden" };
158
+ }
159
+ }
160
+ return true;
161
+ };
162
+ }
163
+ // Annotate the CommonJS export names for ESM import in node:
164
+ 0 && (module.exports = {
165
+ AUTHMS_KEY,
166
+ authmsGuard,
167
+ createAuthms,
168
+ getAuthms,
169
+ useAuthms,
170
+ vAuth
171
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,141 @@
1
+ // src/createAuthms.ts
2
+ import "vue";
3
+ import { AuthMS, browserPlatform } from "@authms/core";
4
+ var AUTHMS_KEY = /* @__PURE__ */ Symbol("authms");
5
+ var _authms = null;
6
+ function getAuthms() {
7
+ return _authms;
8
+ }
9
+ function createAuthms(config) {
10
+ const coreConfig = {
11
+ appId: config.appId,
12
+ issuer: config.issuer,
13
+ apiUrl: config.apiUrl,
14
+ platform: browserPlatform,
15
+ storagePrefix: config.storagePrefix,
16
+ syncTabs: config.syncTabs
17
+ };
18
+ const authms = new AuthMS(coreConfig);
19
+ _authms = authms;
20
+ return {
21
+ install(app) {
22
+ app.provide(AUTHMS_KEY, authms);
23
+ authms.initialize().catch((err) => {
24
+ console.error("[AuthMS Vue] Initialization failed:", err);
25
+ });
26
+ }
27
+ };
28
+ }
29
+
30
+ // src/useAuthms.ts
31
+ import { inject, ref, computed, onMounted, onUnmounted } from "vue";
32
+ import {
33
+ AuthmsError
34
+ } from "@authms/core";
35
+ function useAuthms() {
36
+ const authms = inject(AUTHMS_KEY);
37
+ if (!authms) {
38
+ throw new AuthmsError(
39
+ "CONFIG_ERROR",
40
+ "useAuthms() must be used within a Vue app that has createAuthms() plugin installed",
41
+ 500
42
+ );
43
+ }
44
+ const user = ref(authms.user);
45
+ const isLoading = ref(!authms.isReady());
46
+ const isAuthenticated = computed(() => !!user.value);
47
+ let unsubReady;
48
+ let unsubUser;
49
+ onMounted(() => {
50
+ if (authms.isReady()) {
51
+ isLoading.value = false;
52
+ }
53
+ unsubReady = authms.on("READY", () => {
54
+ isLoading.value = false;
55
+ });
56
+ unsubUser = authms.on("USER_CHANGED", () => {
57
+ user.value = authms.user;
58
+ });
59
+ });
60
+ onUnmounted(() => {
61
+ unsubReady?.();
62
+ unsubUser?.();
63
+ });
64
+ return {
65
+ authms,
66
+ user,
67
+ isLoading,
68
+ isAuthenticated,
69
+ login: (credentials) => authms.login(credentials),
70
+ loginWithOAuth: (options) => authms.loginWithOAuth(options),
71
+ register: (data) => authms.register(data),
72
+ logout: () => authms.logout(),
73
+ getAccessToken: () => authms.getAccessToken()
74
+ };
75
+ }
76
+
77
+ // src/directive.ts
78
+ import "vue";
79
+ function hasRequiredRole(roles) {
80
+ const authms = getAuthms();
81
+ if (!authms?.isAuthenticated()) return false;
82
+ const user = authms.user;
83
+ if (!user) return false;
84
+ const userRole = user.role;
85
+ if (!userRole) return false;
86
+ const required = Array.isArray(roles) ? roles : [roles];
87
+ return required.length === 0 || required.includes(userRole);
88
+ }
89
+ function updateVisibility(el, binding) {
90
+ const visible = hasRequiredRole(binding.value);
91
+ if (visible) {
92
+ if (el.style.display === "none") {
93
+ el.style.display = el.__vAuthOrigDisplay ?? "";
94
+ }
95
+ } else {
96
+ if (el.style.display !== "none") {
97
+ el.__vAuthOrigDisplay = el.style.display;
98
+ el.style.display = "none";
99
+ }
100
+ }
101
+ }
102
+ var vAuth = {
103
+ mounted(el, binding) {
104
+ updateVisibility(el, binding);
105
+ },
106
+ updated(el, binding) {
107
+ if (binding.value !== binding.oldValue) {
108
+ updateVisibility(el, binding);
109
+ }
110
+ }
111
+ };
112
+
113
+ // src/guard.ts
114
+ function authmsGuard(roles) {
115
+ return (to) => {
116
+ const authms = getAuthms();
117
+ if (!authms || !authms.isReady()) {
118
+ return true;
119
+ }
120
+ if (!authms.isAuthenticated()) {
121
+ if (to.path === "/login") return true;
122
+ return { path: "/login", query: { redirect: to.fullPath } };
123
+ }
124
+ if (roles && roles.length > 0) {
125
+ const user = authms.user;
126
+ const userRole = user?.["role"];
127
+ if (!userRole || !roles.includes(userRole)) {
128
+ return { path: "/forbidden" };
129
+ }
130
+ }
131
+ return true;
132
+ };
133
+ }
134
+ export {
135
+ AUTHMS_KEY,
136
+ authmsGuard,
137
+ createAuthms,
138
+ getAuthms,
139
+ useAuthms,
140
+ vAuth
141
+ };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@autional/vue",
3
+ "version": "0.2.0",
4
+ "description": "AuthMS Vue SDK — createAuthms plugin, useAuthms composable, vAuth directive, and authmsGuard router guard",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "sideEffects": false,
16
+ "scripts": {
17
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean --external @autional/core --external vue",
18
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch --external @autional/core --external vue",
19
+ "typecheck": "tsc --noEmit",
20
+ "test": "vitest run",
21
+ "clean": "rimraf dist"
22
+ },
23
+ "files": ["dist", "src"],
24
+ "keywords": ["auth", "vue", "authentication", "authms"],
25
+ "license": "MIT",
26
+ "peerDependencies": {
27
+ "@autional/core": ">=0.1.0",
28
+ "vue": ">=3.0.0"
29
+ },
30
+ "dependencies": {
31
+ "@autional/core": ">=0.1.0"
32
+ },
33
+ "devDependencies": {
34
+ "@autional/tsconfig": ">=0.1.0",
35
+ "rimraf": "^5.0.0",
36
+ "tsup": "^8.4.0",
37
+ "typescript": "^5.8.0",
38
+ "vitest": "^3.0.0"
39
+ }
40
+ }
@@ -0,0 +1,160 @@
1
+ // @ts-nocheck — vitest mock types
2
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
3
+
4
+ const { mockVueInject, mockVueRef, mockVueComputed, mockVueOnMounted, mockVueOnUnmounted } = vi.hoisted(() => ({
5
+ mockVueInject: vi.fn(),
6
+ mockVueRef: vi.fn((initial: any) => ({ value: initial })),
7
+ mockVueComputed: vi.fn((fn: () => any) => ({ value: fn() })),
8
+ mockVueOnMounted: vi.fn((fn: () => void) => fn()),
9
+ mockVueOnUnmounted: vi.fn(),
10
+ }));
11
+
12
+ vi.mock('vue', () => ({
13
+ inject: mockVueInject,
14
+ ref: mockVueRef,
15
+ computed: mockVueComputed,
16
+ onMounted: mockVueOnMounted,
17
+ onUnmounted: mockVueOnUnmounted,
18
+ }));
19
+
20
+ vi.mock('@authms/core', () => {
21
+ const mockAuthMS = vi.fn().mockImplementation(function (this: any) {
22
+ this.on = vi.fn().mockReturnValue(() => {});
23
+ this.initialize = vi.fn().mockResolvedValue(undefined);
24
+ this.login = vi.fn().mockResolvedValue({});
25
+ this.loginWithOAuth = vi.fn().mockResolvedValue(undefined);
26
+ this.register = vi.fn().mockResolvedValue({});
27
+ this.logout = vi.fn().mockResolvedValue(undefined);
28
+ this.isReady = vi.fn().mockReturnValue(true);
29
+ this.isAuthenticated = vi.fn().mockReturnValue(false);
30
+ this.getAccessToken = vi.fn().mockResolvedValue(null);
31
+ this.user = null;
32
+ });
33
+ return {
34
+ AuthMS: mockAuthMS,
35
+ browserPlatform: {},
36
+ AuthmsError: class extends Error {
37
+ code = '';
38
+ status = 0;
39
+ constructor(c: string, m: string, s: number) {
40
+ super(m);
41
+ this.code = c;
42
+ this.status = s;
43
+ }
44
+ },
45
+ };
46
+ });
47
+
48
+ import { createAuthms, getAuthms } from '../createAuthms';
49
+ import { useAuthms } from '../useAuthms';
50
+ import { authmsGuard } from '../guard';
51
+
52
+ describe('createAuthms', () => {
53
+ it('returns a Vue Plugin with install method', () => {
54
+ const plugin = createAuthms({ appId: 'test', issuer: 'https://auth.example.com' });
55
+
56
+ expect(plugin).toHaveProperty('install');
57
+ expect(typeof plugin.install).toBe('function');
58
+ });
59
+
60
+ it('install calls app.provide with the authms instance', () => {
61
+ const plugin = createAuthms({ appId: 'test', issuer: 'https://auth.example.com' });
62
+ const mockApp = { provide: vi.fn() };
63
+
64
+ plugin.install(mockApp as any);
65
+
66
+ expect(mockApp.provide).toHaveBeenCalled();
67
+ const [key, instance] = mockApp.provide.mock.calls[0];
68
+ expect(instance).toBeDefined();
69
+ });
70
+ });
71
+
72
+ describe('useAuthms', () => {
73
+ let mockAuthms: any;
74
+
75
+ beforeEach(() => {
76
+ mockAuthms = {
77
+ on: vi.fn().mockReturnValue(() => {}),
78
+ initialize: vi.fn().mockResolvedValue(undefined),
79
+ login: vi.fn().mockResolvedValue({ access_token: 'token' }),
80
+ loginWithOAuth: vi.fn().mockResolvedValue(undefined),
81
+ register: vi.fn().mockResolvedValue({ access_token: 'token' }),
82
+ logout: vi.fn().mockResolvedValue(undefined),
83
+ isReady: vi.fn().mockReturnValue(true),
84
+ isAuthenticated: vi.fn().mockReturnValue(true),
85
+ getAccessToken: vi.fn().mockResolvedValue('token'),
86
+ user: { sub: 'user-1', email: 'test@test.com' },
87
+ };
88
+
89
+ mockVueInject.mockReturnValue(mockAuthms);
90
+ });
91
+
92
+ afterEach(() => {
93
+ vi.clearAllMocks();
94
+ });
95
+
96
+ it('returns reactive user, isLoading, isAuthenticated', () => {
97
+ const result = useAuthms();
98
+
99
+ expect(result).toHaveProperty('authms');
100
+ expect(result).toHaveProperty('user');
101
+ expect(result).toHaveProperty('isLoading');
102
+ expect(result).toHaveProperty('isAuthenticated');
103
+ expect(result).toHaveProperty('login');
104
+ expect(result).toHaveProperty('logout');
105
+ expect(result).toHaveProperty('getAccessToken');
106
+ });
107
+
108
+ it('login method delegates to authms.login', async () => {
109
+ const result = useAuthms();
110
+ const credentials = { email: 'test@test.com', password: 'secret' };
111
+
112
+ await result.login(credentials as any);
113
+
114
+ expect(mockAuthms.login).toHaveBeenCalledWith(credentials);
115
+ });
116
+
117
+ it('logout delegates to authms.logout', async () => {
118
+ const result = useAuthms();
119
+
120
+ await result.logout();
121
+
122
+ expect(mockAuthms.logout).toHaveBeenCalled();
123
+ });
124
+ });
125
+
126
+ describe('getAuthms', () => {
127
+ let freshCreateAuthms: typeof createAuthms;
128
+ let freshGetAuthms: typeof getAuthms;
129
+
130
+ beforeEach(async () => {
131
+ vi.resetModules();
132
+ const mod = await import('../createAuthms');
133
+ freshCreateAuthms = mod.createAuthms;
134
+ freshGetAuthms = mod.getAuthms;
135
+ });
136
+
137
+ it('returns null before createAuthms', () => {
138
+ expect(freshGetAuthms()).toBeNull();
139
+ });
140
+
141
+ it('returns instance after createAuthms', () => {
142
+ freshCreateAuthms({ appId: 'test', issuer: 'https://auth.example.com' });
143
+
144
+ expect(freshGetAuthms()).not.toBeNull();
145
+ });
146
+ });
147
+
148
+ describe('authmsGuard', () => {
149
+ beforeEach(async () => {
150
+ vi.resetModules();
151
+ const mod = await import('../createAuthms');
152
+ mod.createAuthms({ appId: 'test', issuer: 'https://auth.example.com' });
153
+ });
154
+
155
+ it('returns a function for router use', () => {
156
+ const guard = authmsGuard();
157
+
158
+ expect(typeof guard).toBe('function');
159
+ });
160
+ });
@@ -0,0 +1,42 @@
1
+ import { type App, type InjectionKey, type Plugin } from 'vue';
2
+ import { AuthMS, browserPlatform, type AuthmsConfig as CoreConfig } from '@authms/core';
3
+
4
+ export interface VueAuthmsConfig {
5
+ appId: string;
6
+ issuer: string;
7
+ apiUrl?: string;
8
+ storagePrefix?: string;
9
+ syncTabs?: boolean;
10
+ }
11
+
12
+ export const AUTHMS_KEY: InjectionKey<AuthMS> = Symbol('authms');
13
+
14
+ let _authms: AuthMS | null = null;
15
+
16
+ export function getAuthms(): AuthMS | null {
17
+ return _authms;
18
+ }
19
+
20
+ export function createAuthms(config: VueAuthmsConfig): Plugin {
21
+ const coreConfig: CoreConfig = {
22
+ appId: config.appId,
23
+ issuer: config.issuer,
24
+ apiUrl: config.apiUrl,
25
+ platform: browserPlatform,
26
+ storagePrefix: config.storagePrefix,
27
+ syncTabs: config.syncTabs,
28
+ };
29
+
30
+ const authms = new AuthMS(coreConfig);
31
+ _authms = authms;
32
+
33
+ return {
34
+ install(app: App) {
35
+ app.provide(AUTHMS_KEY, authms);
36
+
37
+ authms.initialize().catch((err: unknown) => {
38
+ console.error('[AuthMS Vue] Initialization failed:', err);
39
+ });
40
+ },
41
+ };
42
+ }
@@ -0,0 +1,46 @@
1
+ import { type Directive, type DirectiveBinding } from 'vue';
2
+ import { getAuthms } from './createAuthms';
3
+
4
+ type AuthRoles = string | string[];
5
+
6
+ function hasRequiredRole(roles: AuthRoles): boolean {
7
+ const authms = getAuthms();
8
+ if (!authms?.isAuthenticated()) return false;
9
+
10
+ const user = authms.user;
11
+ if (!user) return false;
12
+
13
+ const userRole = user.role as string | undefined;
14
+ if (!userRole) return false;
15
+
16
+ const required = Array.isArray(roles) ? roles : [roles];
17
+ return required.length === 0 || required.includes(userRole);
18
+ }
19
+
20
+ function updateVisibility(el: HTMLElement, binding: DirectiveBinding<AuthRoles>): void {
21
+ const visible = hasRequiredRole(binding.value);
22
+
23
+ if (visible) {
24
+ if (el.style.display === 'none') {
25
+ (el as HTMLElement & { __vAuthOrigDisplay?: string }).style.display =
26
+ (el as HTMLElement & { __vAuthOrigDisplay?: string }).__vAuthOrigDisplay ?? '';
27
+ }
28
+ } else {
29
+ if (el.style.display !== 'none') {
30
+ (el as HTMLElement & { __vAuthOrigDisplay?: string }).__vAuthOrigDisplay = el.style.display;
31
+ el.style.display = 'none';
32
+ }
33
+ }
34
+ }
35
+
36
+ export const vAuth: Directive<HTMLElement, AuthRoles> = {
37
+ mounted(el, binding) {
38
+ updateVisibility(el, binding);
39
+ },
40
+
41
+ updated(el, binding) {
42
+ if (binding.value !== binding.oldValue) {
43
+ updateVisibility(el, binding);
44
+ }
45
+ },
46
+ };
package/src/guard.ts ADDED
@@ -0,0 +1,39 @@
1
+ import { getAuthms } from './createAuthms';
2
+
3
+ interface RouteLocation {
4
+ path: string;
5
+ fullPath: string;
6
+ query: Record<string, unknown>;
7
+ }
8
+
9
+ type GuardReturn =
10
+ | boolean
11
+ | { path: string; query?: Record<string, string> };
12
+
13
+ type NavigationGuard = (to: RouteLocation, from: RouteLocation) => GuardReturn;
14
+
15
+ export function authmsGuard(roles?: string[]): NavigationGuard {
16
+ return (to) => {
17
+ const authms = getAuthms();
18
+
19
+ if (!authms || !authms.isReady()) {
20
+ return true;
21
+ }
22
+
23
+ if (!authms.isAuthenticated()) {
24
+ if (to.path === '/login') return true;
25
+ return { path: '/login', query: { redirect: to.fullPath } };
26
+ }
27
+
28
+ if (roles && roles.length > 0) {
29
+ const user = authms.user;
30
+ const userRole = (user as Record<string, unknown> | null)?.['role'] as string | undefined;
31
+
32
+ if (!userRole || !roles.includes(userRole)) {
33
+ return { path: '/forbidden' };
34
+ }
35
+ }
36
+
37
+ return true;
38
+ };
39
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export { createAuthms, getAuthms, AUTHMS_KEY } from './createAuthms';
2
+ export type { VueAuthmsConfig } from './createAuthms';
3
+ export { useAuthms } from './useAuthms';
4
+ export type { UseAuthmsReturn } from './useAuthms';
5
+ export { vAuth } from './directive';
6
+ export { authmsGuard } from './guard';
@@ -0,0 +1,73 @@
1
+ import { inject, ref, computed, onMounted, onUnmounted, type Ref, type ComputedRef } from 'vue';
2
+ import { AUTHMS_KEY } from './createAuthms';
3
+ import {
4
+ AuthMS,
5
+ AuthmsError,
6
+ type User,
7
+ type AuthResult,
8
+ type LoginRequest,
9
+ type RegisterRequest,
10
+ type OAuthOptions,
11
+ } from '@authms/core';
12
+
13
+ export interface UseAuthmsReturn {
14
+ authms: AuthMS;
15
+ user: Ref<User | null>;
16
+ isLoading: Ref<boolean>;
17
+ isAuthenticated: ComputedRef<boolean>;
18
+ login: (credentials: LoginRequest) => Promise<AuthResult>;
19
+ loginWithOAuth: (options: OAuthOptions) => Promise<void>;
20
+ register: (data: RegisterRequest) => Promise<AuthResult>;
21
+ logout: () => Promise<void>;
22
+ getAccessToken: () => Promise<string | null>;
23
+ }
24
+
25
+ export function useAuthms(): UseAuthmsReturn {
26
+ const authms = inject(AUTHMS_KEY);
27
+
28
+ if (!authms) {
29
+ throw new AuthmsError(
30
+ 'CONFIG_ERROR',
31
+ 'useAuthms() must be used within a Vue app that has createAuthms() plugin installed',
32
+ 500,
33
+ );
34
+ }
35
+
36
+ const user = ref<User | null>(authms.user);
37
+ const isLoading = ref<boolean>(!authms.isReady());
38
+ const isAuthenticated = computed(() => !!user.value);
39
+
40
+ let unsubReady: (() => void) | undefined;
41
+ let unsubUser: (() => void) | undefined;
42
+
43
+ onMounted(() => {
44
+ if (authms.isReady()) {
45
+ isLoading.value = false;
46
+ }
47
+
48
+ unsubReady = authms.on('READY', () => {
49
+ isLoading.value = false;
50
+ });
51
+
52
+ unsubUser = authms.on('USER_CHANGED', () => {
53
+ user.value = authms.user;
54
+ });
55
+ });
56
+
57
+ onUnmounted(() => {
58
+ unsubReady?.();
59
+ unsubUser?.();
60
+ });
61
+
62
+ return {
63
+ authms,
64
+ user,
65
+ isLoading,
66
+ isAuthenticated,
67
+ login: (credentials) => authms.login(credentials),
68
+ loginWithOAuth: (options) => authms.loginWithOAuth(options),
69
+ register: (data) => authms.register(data),
70
+ logout: () => authms.logout(),
71
+ getAccessToken: () => authms.getAccessToken(),
72
+ };
73
+ }