@phila/sso-vue 0.0.2 → 0.0.4
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 +167 -0
- package/dist/index.js +132 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +96 -71
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# @phila/sso-vue
|
|
2
|
+
|
|
3
|
+
Vue 3 adapter for `@phila/sso-core`. Provides a Vue plugin, Pinia store, and composables for Azure AD B2C authentication.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @phila/sso-vue @phila/sso-core @azure/msal-browser pinia
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start (Vite + B2C)
|
|
12
|
+
|
|
13
|
+
The `createB2CPlugin` factory reads `VITE_SSO_*` environment variables automatically:
|
|
14
|
+
|
|
15
|
+
```env
|
|
16
|
+
VITE_SSO_CLIENT_ID=your-client-id
|
|
17
|
+
VITE_SSO_TENANT=YourTenant
|
|
18
|
+
VITE_SSO_AUTHORITY_DOMAIN=YourTenant.b2clogin.com
|
|
19
|
+
VITE_SSO_REDIRECT_URI=http://localhost:3000
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
// main.ts
|
|
24
|
+
import { createApp } from "vue";
|
|
25
|
+
import { createPinia } from "pinia";
|
|
26
|
+
import { createB2CPlugin } from "@phila/sso-vue";
|
|
27
|
+
import App from "./App.vue";
|
|
28
|
+
|
|
29
|
+
const app = createApp(App);
|
|
30
|
+
app.use(createPinia());
|
|
31
|
+
app.use(createB2CPlugin());
|
|
32
|
+
app.mount("#app");
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```vue
|
|
36
|
+
<!-- App.vue -->
|
|
37
|
+
<script setup lang="ts">
|
|
38
|
+
import { useAuth } from "@phila/sso-vue";
|
|
39
|
+
|
|
40
|
+
const { isAuthenticated, userName, authReady, signIn, signOut } = useAuth();
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<template>
|
|
44
|
+
<div v-if="!authReady">Loading...</div>
|
|
45
|
+
<div v-else-if="isAuthenticated">
|
|
46
|
+
<p>Welcome, {{ userName }}</p>
|
|
47
|
+
<button @click="signOut()">Sign out</button>
|
|
48
|
+
</div>
|
|
49
|
+
<div v-else>
|
|
50
|
+
<button @click="signIn()">Sign in</button>
|
|
51
|
+
</div>
|
|
52
|
+
</template>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Advanced Setup
|
|
56
|
+
|
|
57
|
+
For full control over the provider configuration:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { createSSOPlugin } from "@phila/sso-vue";
|
|
61
|
+
import { B2CProvider } from "@phila/sso-core";
|
|
62
|
+
|
|
63
|
+
app.use(
|
|
64
|
+
createSSOPlugin({
|
|
65
|
+
clientConfig: {
|
|
66
|
+
provider: new B2CProvider({
|
|
67
|
+
clientId: "your-client-id",
|
|
68
|
+
b2cEnvironment: "YourTenant",
|
|
69
|
+
authorityDomain: "YourTenant.b2clogin.com",
|
|
70
|
+
redirectUri: "http://localhost:3000",
|
|
71
|
+
apiScopes: ["https://YourTenant.onmicrosoft.com/api/read"],
|
|
72
|
+
policies: {
|
|
73
|
+
signUpSignIn: "B2C_1A_SIGNUP_SIGNIN",
|
|
74
|
+
signInOnly: "B2C_1A_AD_SIGNIN_ONLY",
|
|
75
|
+
resetPassword: "B2C_1A_PASSWORDRESET",
|
|
76
|
+
},
|
|
77
|
+
}),
|
|
78
|
+
debug: true,
|
|
79
|
+
},
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Plugin Options
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
interface SSOPluginOptions {
|
|
88
|
+
clientConfig: SSOClientConfig;
|
|
89
|
+
autoInitialize?: boolean; // default: true
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Composables
|
|
94
|
+
|
|
95
|
+
### `useAuth()`
|
|
96
|
+
|
|
97
|
+
Primary composable for authentication. Must be called after the plugin is installed.
|
|
98
|
+
|
|
99
|
+
**Reactive state:**
|
|
100
|
+
|
|
101
|
+
| Property | Type | Description |
|
|
102
|
+
| ----------------- | ----------------------------- | ------------------------------ |
|
|
103
|
+
| `isAuthenticated` | `Ref<boolean>` | User is signed in |
|
|
104
|
+
| `isLoading` | `Ref<boolean>` | Auth operation in progress |
|
|
105
|
+
| `user` | `Ref<AccountInfo \| null>` | MSAL account info |
|
|
106
|
+
| `token` | `Ref<string \| null>` | Current access token |
|
|
107
|
+
| `error` | `Ref<Error \| null>` | Last auth error |
|
|
108
|
+
| `activePolicy` | `Ref<string \| null>` | Active B2C policy |
|
|
109
|
+
| `authReady` | `Ref<boolean>` | Initialization complete |
|
|
110
|
+
| `userName` | `ComputedRef<string \| null>` | Display name from token claims |
|
|
111
|
+
|
|
112
|
+
**Actions:**
|
|
113
|
+
|
|
114
|
+
| Method | Returns | Description |
|
|
115
|
+
| ------------------------------ | ------------------------- | -------------------------------- |
|
|
116
|
+
| `signIn(options?)` | `Promise<void>` | Start sign-in flow |
|
|
117
|
+
| `signInCityEmployee(options?)` | `Promise<void>` | Sign in with sign-in-only policy |
|
|
118
|
+
| `signOut(options?)` | `Promise<void>` | Sign out |
|
|
119
|
+
| `forgotPassword()` | `Promise<void>` | Start password reset |
|
|
120
|
+
| `acquireToken(options?)` | `Promise<string \| null>` | Get access token |
|
|
121
|
+
|
|
122
|
+
**Utilities:**
|
|
123
|
+
|
|
124
|
+
| Method | Returns | Description |
|
|
125
|
+
| --------------- | --------- | ------------------------------------------------------- |
|
|
126
|
+
| `hasRole(role)` | `boolean` | Check user role from `roles` or `extension_Roles` claim |
|
|
127
|
+
|
|
128
|
+
### `useSSOClient()`
|
|
129
|
+
|
|
130
|
+
Returns the raw `SSOClient` instance for advanced use cases (direct event subscription, etc.).
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
const client = useSSOClient();
|
|
134
|
+
client.events.on("auth:tokenAcquired", token => {
|
|
135
|
+
/* ... */
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### `useSSOStore()`
|
|
140
|
+
|
|
141
|
+
Direct access to the Pinia store. Useful when you need store-level reactivity outside of components.
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import { useSSOStore } from "@phila/sso-vue";
|
|
145
|
+
|
|
146
|
+
const store = useSSOStore();
|
|
147
|
+
watch(
|
|
148
|
+
() => store.isAuthenticated,
|
|
149
|
+
val => {
|
|
150
|
+
/* ... */
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## `createB2CPlugin` Options
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
interface B2CPluginOptions {
|
|
159
|
+
signInPolicy?: string; // default: "B2C_1A_AD_SIGNIN_ONLY"
|
|
160
|
+
resetPasswordPolicy?: string; // default: "B2C_1A_PASSWORDRESET"
|
|
161
|
+
debug?: boolean; // default: import.meta.env.DEV
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## License
|
|
166
|
+
|
|
167
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,133 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const vue = require("vue");
|
|
4
|
+
const pinia = require("pinia");
|
|
5
|
+
const ssoCore = require("@phila/sso-core");
|
|
6
|
+
const SSO_CLIENT_KEY = /* @__PURE__ */ Symbol("sso-client");
|
|
7
|
+
const useSSOStore = pinia.defineStore("sso", () => {
|
|
8
|
+
const isAuthenticated = vue.ref(false);
|
|
9
|
+
const isLoading = vue.ref(false);
|
|
10
|
+
const user = vue.ref(null);
|
|
11
|
+
const token = vue.ref(null);
|
|
12
|
+
const error = vue.ref(null);
|
|
13
|
+
const activePolicy = vue.ref(null);
|
|
14
|
+
const authReady = vue.ref(false);
|
|
15
|
+
function syncState(state) {
|
|
16
|
+
isAuthenticated.value = state.isAuthenticated;
|
|
17
|
+
isLoading.value = state.isLoading;
|
|
18
|
+
user.value = state.user;
|
|
19
|
+
token.value = state.token;
|
|
20
|
+
error.value = state.error;
|
|
21
|
+
activePolicy.value = state.activePolicy;
|
|
22
|
+
authReady.value = state.authReady;
|
|
23
|
+
}
|
|
24
|
+
return { isAuthenticated, isLoading, user, token, error, activePolicy, authReady, syncState };
|
|
25
|
+
});
|
|
26
|
+
function createSSOPlugin(options) {
|
|
27
|
+
return {
|
|
28
|
+
install(app) {
|
|
29
|
+
const client = new ssoCore.SSOClient(options.clientConfig);
|
|
30
|
+
app.provide(SSO_CLIENT_KEY, client);
|
|
31
|
+
client.events.on("auth:stateChanged", (state) => {
|
|
32
|
+
const store = useSSOStore();
|
|
33
|
+
store.syncState(state);
|
|
34
|
+
});
|
|
35
|
+
if (options.autoInitialize !== false) {
|
|
36
|
+
client.initialize().then((response) => {
|
|
37
|
+
if (response && options.clientConfig.debug) {
|
|
38
|
+
console.log("[sso-vue] Auto-initialized with response:", response);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function useSSOClient() {
|
|
46
|
+
const client = vue.inject(SSO_CLIENT_KEY);
|
|
47
|
+
if (!client) {
|
|
48
|
+
throw new Error("SSOClient not found. Did you install createSSOPlugin?");
|
|
49
|
+
}
|
|
50
|
+
return client;
|
|
51
|
+
}
|
|
52
|
+
function useAuth() {
|
|
53
|
+
const store = useSSOStore();
|
|
54
|
+
const client = useSSOClient();
|
|
55
|
+
const { isAuthenticated, isLoading, user, token, error, activePolicy, authReady } = pinia.storeToRefs(store);
|
|
56
|
+
const userName = vue.computed(() => {
|
|
57
|
+
const claims = user.value?.idTokenClaims;
|
|
58
|
+
if (!claims) return null;
|
|
59
|
+
const given = claims.given_name ?? claims.name ?? "";
|
|
60
|
+
const family = claims.family_name ?? "";
|
|
61
|
+
return family ? `${given} ${family}`.trim() : given;
|
|
62
|
+
});
|
|
63
|
+
async function signIn(options) {
|
|
64
|
+
await client.signIn(options);
|
|
65
|
+
}
|
|
66
|
+
async function signInCityEmployee(options) {
|
|
67
|
+
await client.signInCityEmployee(options);
|
|
68
|
+
}
|
|
69
|
+
async function signOut(options) {
|
|
70
|
+
await client.signOut(options);
|
|
71
|
+
}
|
|
72
|
+
async function forgotPassword() {
|
|
73
|
+
await client.forgotPassword();
|
|
74
|
+
}
|
|
75
|
+
async function acquireToken(options) {
|
|
76
|
+
return client.acquireToken(options);
|
|
77
|
+
}
|
|
78
|
+
function hasRole(role) {
|
|
79
|
+
const claims = user.value?.idTokenClaims;
|
|
80
|
+
if (!claims) return false;
|
|
81
|
+
const roles = claims.roles ?? claims.extension_Roles ?? [];
|
|
82
|
+
return Array.isArray(roles) && roles.includes(role);
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
// State (readonly refs)
|
|
86
|
+
isAuthenticated,
|
|
87
|
+
isLoading,
|
|
88
|
+
user,
|
|
89
|
+
token,
|
|
90
|
+
error,
|
|
91
|
+
activePolicy,
|
|
92
|
+
authReady,
|
|
93
|
+
userName,
|
|
94
|
+
// Actions
|
|
95
|
+
signIn,
|
|
96
|
+
signInCityEmployee,
|
|
97
|
+
signOut,
|
|
98
|
+
forgotPassword,
|
|
99
|
+
acquireToken,
|
|
100
|
+
// Utilities
|
|
101
|
+
hasRole
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function createB2CPlugin(options = {}) {
|
|
105
|
+
const {
|
|
106
|
+
signInPolicy = "B2C_1A_AD_SIGNIN_ONLY",
|
|
107
|
+
resetPasswordPolicy = "B2C_1A_PASSWORDRESET",
|
|
108
|
+
debug = false
|
|
109
|
+
} = options;
|
|
110
|
+
return createSSOPlugin({
|
|
111
|
+
clientConfig: {
|
|
112
|
+
provider: new ssoCore.B2CProvider({
|
|
113
|
+
clientId: void 0,
|
|
114
|
+
b2cEnvironment: void 0,
|
|
115
|
+
authorityDomain: void 0,
|
|
116
|
+
redirectUri: void 0,
|
|
117
|
+
policies: {
|
|
118
|
+
signUpSignIn: signInPolicy,
|
|
119
|
+
signInOnly: signInPolicy,
|
|
120
|
+
resetPassword: resetPasswordPolicy
|
|
121
|
+
}
|
|
122
|
+
}),
|
|
123
|
+
debug
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
exports.SSO_CLIENT_KEY = SSO_CLIENT_KEY;
|
|
128
|
+
exports.createB2CPlugin = createB2CPlugin;
|
|
129
|
+
exports.createSSOPlugin = createSSOPlugin;
|
|
130
|
+
exports.useAuth = useAuth;
|
|
131
|
+
exports.useSSOClient = useSSOClient;
|
|
132
|
+
exports.useSSOStore = useSSOStore;
|
|
2
133
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/plugin.ts","../src/composables/useSSOClient.ts","../src/composables/useAuth.ts","../src/createB2CPlugin.ts"],"sourcesContent":["import { ref } from \"vue\";\nimport { defineStore } from \"pinia\";\nimport { SSOClient } from \"@phila/sso-core\";\nimport type { App } from \"vue\";\nimport type { SSOClientConfig, SSOClientState, AuthResponse } from \"@phila/sso-core\";\n\nexport const SSO_CLIENT_KEY = Symbol(\"sso-client\");\n\nexport interface SSOPluginOptions {\n clientConfig: SSOClientConfig;\n autoInitialize?: boolean;\n}\n\nexport const useSSOStore = defineStore(\"sso\", () => {\n const isAuthenticated = ref(false);\n const isLoading = ref(false);\n const user = ref<SSOClientState[\"user\"]>(null);\n const token = ref<string | null>(null);\n const error = ref<Error | null>(null);\n const activePolicy = ref<string | null>(null);\n const authReady = ref(false);\n\n function syncState(state: SSOClientState) {\n isAuthenticated.value = state.isAuthenticated;\n isLoading.value = state.isLoading;\n user.value = state.user;\n token.value = state.token;\n error.value = state.error;\n activePolicy.value = state.activePolicy;\n authReady.value = state.authReady;\n }\n\n return { isAuthenticated, isLoading, user, token, error, activePolicy, authReady, syncState };\n});\n\nexport function createSSOPlugin(options: SSOPluginOptions) {\n return {\n install(app: App) {\n const client = new SSOClient(options.clientConfig);\n\n // Provide the client for useSSOClient()\n app.provide(SSO_CLIENT_KEY, client);\n\n // Subscribe to state changes and sync to Pinia store\n client.events.on(\"auth:stateChanged\", (state: SSOClientState) => {\n const store = useSSOStore();\n store.syncState(state);\n });\n\n // Auto-initialize unless explicitly disabled\n if (options.autoInitialize !== false) {\n client.initialize().then((response: AuthResponse | null) => {\n if (response && options.clientConfig.debug) {\n console.log(\"[sso-vue] Auto-initialized with response:\", response);\n }\n });\n }\n },\n };\n}\n","import { inject } from \"vue\";\nimport { SSOClient } from \"@phila/sso-core\";\nimport { SSO_CLIENT_KEY } from \"../plugin\";\n\n/**\n * Access the raw SSOClient instance.\n * Must be called within a component that is a descendant of createSSOPlugin.\n */\nexport function useSSOClient(): SSOClient {\n const client = inject<SSOClient>(SSO_CLIENT_KEY);\n if (!client) {\n throw new Error(\"SSOClient not found. Did you install createSSOPlugin?\");\n }\n return client;\n}\n","import { computed } from \"vue\";\nimport { storeToRefs } from \"pinia\";\nimport { useSSOStore } from \"../plugin\";\nimport { useSSOClient } from \"./useSSOClient\";\nimport type { SignInOptions, SignOutOptions, TokenOptions } from \"@phila/sso-core\";\n\n/**\n * Primary composable for SSO authentication in Vue 3 apps.\n * API shape is intentionally similar to service-cat's existing useAuth()\n * to minimize migration friction.\n */\nexport function useAuth() {\n const store = useSSOStore();\n const client = useSSOClient();\n const { isAuthenticated, isLoading, user, token, error, activePolicy, authReady } = storeToRefs(store);\n\n // Computed helpers\n const userName = computed(() => {\n const claims = user.value?.idTokenClaims as Record<string, unknown> | undefined;\n if (!claims) return null;\n const given = (claims.given_name ?? claims.name ?? \"\") as string;\n const family = (claims.family_name ?? \"\") as string;\n return family ? `${given} ${family}`.trim() : given;\n });\n\n // Actions\n async function signIn(options?: SignInOptions): Promise<void> {\n await client.signIn(options);\n }\n\n async function signInCityEmployee(options?: SignInOptions): Promise<void> {\n await client.signInCityEmployee(options);\n }\n\n async function signOut(options?: SignOutOptions): Promise<void> {\n await client.signOut(options);\n }\n\n async function forgotPassword(): Promise<void> {\n await client.forgotPassword();\n }\n\n async function acquireToken(options?: TokenOptions): Promise<string | null> {\n return client.acquireToken(options);\n }\n\n // Utilities\n function hasRole(role: string): boolean {\n const claims = user.value?.idTokenClaims as Record<string, unknown> | undefined;\n if (!claims) return false;\n\n const roles = (claims.roles ?? claims.extension_Roles ?? []) as string[];\n return Array.isArray(roles) && roles.includes(role);\n }\n\n return {\n // State (readonly refs)\n isAuthenticated,\n isLoading,\n user,\n token,\n error,\n activePolicy,\n authReady,\n userName,\n\n // Actions\n signIn,\n signInCityEmployee,\n signOut,\n forgotPassword,\n acquireToken,\n\n // Utilities\n hasRole,\n };\n}\n","// ABOUTME: Convenience factory for vanilla Vue/Vite apps using Azure AD B2C\n// ABOUTME: Reads VITE_SSO_* env vars by convention so apps need zero B2CProvider boilerplate\nimport { B2CProvider } from \"@phila/sso-core\";\nimport { createSSOPlugin } from \"./plugin\";\n\nexport interface B2CPluginOptions {\n signInPolicy?: string;\n resetPasswordPolicy?: string;\n debug?: boolean;\n}\n\n/**\n * Creates a ready-to-use Vue plugin for Azure AD B2C authentication.\n * Reads connection details from VITE_SSO_* environment variables:\n * VITE_SSO_CLIENT_ID, VITE_SSO_TENANT, VITE_SSO_AUTHORITY_DOMAIN, VITE_SSO_REDIRECT_URI\n */\nexport function createB2CPlugin(options: B2CPluginOptions = {}) {\n const {\n signInPolicy = \"B2C_1A_AD_SIGNIN_ONLY\",\n resetPasswordPolicy = \"B2C_1A_PASSWORDRESET\",\n debug = import.meta.env.DEV,\n } = options;\n\n return createSSOPlugin({\n clientConfig: {\n provider: new B2CProvider({\n clientId: import.meta.env.VITE_SSO_CLIENT_ID,\n b2cEnvironment: import.meta.env.VITE_SSO_TENANT,\n authorityDomain: import.meta.env.VITE_SSO_AUTHORITY_DOMAIN,\n redirectUri: import.meta.env.VITE_SSO_REDIRECT_URI,\n policies: {\n signUpSignIn: signInPolicy,\n signInOnly: signInPolicy,\n resetPassword: resetPasswordPolicy,\n },\n }),\n debug,\n },\n });\n}\n"],"names":["
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/plugin.ts","../src/composables/useSSOClient.ts","../src/composables/useAuth.ts","../src/createB2CPlugin.ts"],"sourcesContent":["import { ref } from \"vue\";\nimport { defineStore } from \"pinia\";\nimport { SSOClient } from \"@phila/sso-core\";\nimport type { App } from \"vue\";\nimport type { SSOClientConfig, SSOClientState, AuthResponse } from \"@phila/sso-core\";\n\nexport const SSO_CLIENT_KEY = Symbol(\"sso-client\");\n\nexport interface SSOPluginOptions {\n clientConfig: SSOClientConfig;\n autoInitialize?: boolean;\n}\n\nexport const useSSOStore = defineStore(\"sso\", () => {\n const isAuthenticated = ref(false);\n const isLoading = ref(false);\n const user = ref<SSOClientState[\"user\"]>(null);\n const token = ref<string | null>(null);\n const error = ref<Error | null>(null);\n const activePolicy = ref<string | null>(null);\n const authReady = ref(false);\n\n function syncState(state: SSOClientState) {\n isAuthenticated.value = state.isAuthenticated;\n isLoading.value = state.isLoading;\n user.value = state.user;\n token.value = state.token;\n error.value = state.error;\n activePolicy.value = state.activePolicy;\n authReady.value = state.authReady;\n }\n\n return { isAuthenticated, isLoading, user, token, error, activePolicy, authReady, syncState };\n});\n\nexport function createSSOPlugin(options: SSOPluginOptions) {\n return {\n install(app: App) {\n const client = new SSOClient(options.clientConfig);\n\n // Provide the client for useSSOClient()\n app.provide(SSO_CLIENT_KEY, client);\n\n // Subscribe to state changes and sync to Pinia store\n client.events.on(\"auth:stateChanged\", (state: SSOClientState) => {\n const store = useSSOStore();\n store.syncState(state);\n });\n\n // Auto-initialize unless explicitly disabled\n if (options.autoInitialize !== false) {\n client.initialize().then((response: AuthResponse | null) => {\n if (response && options.clientConfig.debug) {\n console.log(\"[sso-vue] Auto-initialized with response:\", response);\n }\n });\n }\n },\n };\n}\n","import { inject } from \"vue\";\nimport { SSOClient } from \"@phila/sso-core\";\nimport { SSO_CLIENT_KEY } from \"../plugin\";\n\n/**\n * Access the raw SSOClient instance.\n * Must be called within a component that is a descendant of createSSOPlugin.\n */\nexport function useSSOClient(): SSOClient {\n const client = inject<SSOClient>(SSO_CLIENT_KEY);\n if (!client) {\n throw new Error(\"SSOClient not found. Did you install createSSOPlugin?\");\n }\n return client;\n}\n","import { computed } from \"vue\";\nimport { storeToRefs } from \"pinia\";\nimport { useSSOStore } from \"../plugin\";\nimport { useSSOClient } from \"./useSSOClient\";\nimport type { SignInOptions, SignOutOptions, TokenOptions } from \"@phila/sso-core\";\n\n/**\n * Primary composable for SSO authentication in Vue 3 apps.\n * API shape is intentionally similar to service-cat's existing useAuth()\n * to minimize migration friction.\n */\nexport function useAuth() {\n const store = useSSOStore();\n const client = useSSOClient();\n const { isAuthenticated, isLoading, user, token, error, activePolicy, authReady } = storeToRefs(store);\n\n // Computed helpers\n const userName = computed(() => {\n const claims = user.value?.idTokenClaims as Record<string, unknown> | undefined;\n if (!claims) return null;\n const given = (claims.given_name ?? claims.name ?? \"\") as string;\n const family = (claims.family_name ?? \"\") as string;\n return family ? `${given} ${family}`.trim() : given;\n });\n\n // Actions\n async function signIn(options?: SignInOptions): Promise<void> {\n await client.signIn(options);\n }\n\n async function signInCityEmployee(options?: SignInOptions): Promise<void> {\n await client.signInCityEmployee(options);\n }\n\n async function signOut(options?: SignOutOptions): Promise<void> {\n await client.signOut(options);\n }\n\n async function forgotPassword(): Promise<void> {\n await client.forgotPassword();\n }\n\n async function acquireToken(options?: TokenOptions): Promise<string | null> {\n return client.acquireToken(options);\n }\n\n // Utilities\n function hasRole(role: string): boolean {\n const claims = user.value?.idTokenClaims as Record<string, unknown> | undefined;\n if (!claims) return false;\n\n const roles = (claims.roles ?? claims.extension_Roles ?? []) as string[];\n return Array.isArray(roles) && roles.includes(role);\n }\n\n return {\n // State (readonly refs)\n isAuthenticated,\n isLoading,\n user,\n token,\n error,\n activePolicy,\n authReady,\n userName,\n\n // Actions\n signIn,\n signInCityEmployee,\n signOut,\n forgotPassword,\n acquireToken,\n\n // Utilities\n hasRole,\n };\n}\n","// ABOUTME: Convenience factory for vanilla Vue/Vite apps using Azure AD B2C\n// ABOUTME: Reads VITE_SSO_* env vars by convention so apps need zero B2CProvider boilerplate\nimport { B2CProvider } from \"@phila/sso-core\";\nimport { createSSOPlugin } from \"./plugin\";\n\nexport interface B2CPluginOptions {\n signInPolicy?: string;\n resetPasswordPolicy?: string;\n debug?: boolean;\n}\n\n/**\n * Creates a ready-to-use Vue plugin for Azure AD B2C authentication.\n * Reads connection details from VITE_SSO_* environment variables:\n * VITE_SSO_CLIENT_ID, VITE_SSO_TENANT, VITE_SSO_AUTHORITY_DOMAIN, VITE_SSO_REDIRECT_URI\n */\nexport function createB2CPlugin(options: B2CPluginOptions = {}) {\n const {\n signInPolicy = \"B2C_1A_AD_SIGNIN_ONLY\",\n resetPasswordPolicy = \"B2C_1A_PASSWORDRESET\",\n debug = import.meta.env.DEV,\n } = options;\n\n return createSSOPlugin({\n clientConfig: {\n provider: new B2CProvider({\n clientId: import.meta.env.VITE_SSO_CLIENT_ID,\n b2cEnvironment: import.meta.env.VITE_SSO_TENANT,\n authorityDomain: import.meta.env.VITE_SSO_AUTHORITY_DOMAIN,\n redirectUri: import.meta.env.VITE_SSO_REDIRECT_URI,\n policies: {\n signUpSignIn: signInPolicy,\n signInOnly: signInPolicy,\n resetPassword: resetPasswordPolicy,\n },\n }),\n debug,\n },\n });\n}\n"],"names":["defineStore","ref","SSOClient","inject","storeToRefs","computed","B2CProvider"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,108 +1,133 @@
|
|
|
1
|
-
import { ref
|
|
2
|
-
import { defineStore
|
|
3
|
-
import { SSOClient
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { ref, inject, computed } from "vue";
|
|
2
|
+
import { defineStore, storeToRefs } from "pinia";
|
|
3
|
+
import { SSOClient, B2CProvider } from "@phila/sso-core";
|
|
4
|
+
const SSO_CLIENT_KEY = /* @__PURE__ */ Symbol("sso-client");
|
|
5
|
+
const useSSOStore = defineStore("sso", () => {
|
|
6
|
+
const isAuthenticated = ref(false);
|
|
7
|
+
const isLoading = ref(false);
|
|
8
|
+
const user = ref(null);
|
|
9
|
+
const token = ref(null);
|
|
10
|
+
const error = ref(null);
|
|
11
|
+
const activePolicy = ref(null);
|
|
12
|
+
const authReady = ref(false);
|
|
13
|
+
function syncState(state) {
|
|
14
|
+
isAuthenticated.value = state.isAuthenticated;
|
|
15
|
+
isLoading.value = state.isLoading;
|
|
16
|
+
user.value = state.user;
|
|
17
|
+
token.value = state.token;
|
|
18
|
+
error.value = state.error;
|
|
19
|
+
activePolicy.value = state.activePolicy;
|
|
20
|
+
authReady.value = state.authReady;
|
|
8
21
|
}
|
|
9
|
-
return { isAuthenticated
|
|
22
|
+
return { isAuthenticated, isLoading, user, token, error, activePolicy, authReady, syncState };
|
|
10
23
|
});
|
|
11
|
-
function
|
|
24
|
+
function createSSOPlugin(options) {
|
|
12
25
|
return {
|
|
13
|
-
install(
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
install(app) {
|
|
27
|
+
const client = new SSOClient(options.clientConfig);
|
|
28
|
+
app.provide(SSO_CLIENT_KEY, client);
|
|
29
|
+
client.events.on("auth:stateChanged", (state) => {
|
|
30
|
+
const store = useSSOStore();
|
|
31
|
+
store.syncState(state);
|
|
19
32
|
});
|
|
33
|
+
if (options.autoInitialize !== false) {
|
|
34
|
+
client.initialize().then((response) => {
|
|
35
|
+
if (response && options.clientConfig.debug) {
|
|
36
|
+
console.log("[sso-vue] Auto-initialized with response:", response);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
}
|
|
20
40
|
}
|
|
21
41
|
};
|
|
22
42
|
}
|
|
23
|
-
function
|
|
24
|
-
const
|
|
25
|
-
if (!
|
|
43
|
+
function useSSOClient() {
|
|
44
|
+
const client = inject(SSO_CLIENT_KEY);
|
|
45
|
+
if (!client) {
|
|
26
46
|
throw new Error("SSOClient not found. Did you install createSSOPlugin?");
|
|
27
|
-
|
|
47
|
+
}
|
|
48
|
+
return client;
|
|
28
49
|
}
|
|
29
|
-
function
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
50
|
+
function useAuth() {
|
|
51
|
+
const store = useSSOStore();
|
|
52
|
+
const client = useSSOClient();
|
|
53
|
+
const { isAuthenticated, isLoading, user, token, error, activePolicy, authReady } = storeToRefs(store);
|
|
54
|
+
const userName = computed(() => {
|
|
55
|
+
const claims = user.value?.idTokenClaims;
|
|
56
|
+
if (!claims) return null;
|
|
57
|
+
const given = claims.given_name ?? claims.name ?? "";
|
|
58
|
+
const family = claims.family_name ?? "";
|
|
59
|
+
return family ? `${given} ${family}`.trim() : given;
|
|
35
60
|
});
|
|
36
|
-
async function
|
|
37
|
-
await
|
|
61
|
+
async function signIn(options) {
|
|
62
|
+
await client.signIn(options);
|
|
38
63
|
}
|
|
39
|
-
async function
|
|
40
|
-
await
|
|
64
|
+
async function signInCityEmployee(options) {
|
|
65
|
+
await client.signInCityEmployee(options);
|
|
41
66
|
}
|
|
42
|
-
async function
|
|
43
|
-
await
|
|
67
|
+
async function signOut(options) {
|
|
68
|
+
await client.signOut(options);
|
|
44
69
|
}
|
|
45
|
-
async function
|
|
46
|
-
await
|
|
70
|
+
async function forgotPassword() {
|
|
71
|
+
await client.forgotPassword();
|
|
47
72
|
}
|
|
48
|
-
async function
|
|
49
|
-
return
|
|
73
|
+
async function acquireToken(options) {
|
|
74
|
+
return client.acquireToken(options);
|
|
50
75
|
}
|
|
51
|
-
function
|
|
52
|
-
const
|
|
53
|
-
if (!
|
|
54
|
-
const
|
|
55
|
-
return Array.isArray(
|
|
76
|
+
function hasRole(role) {
|
|
77
|
+
const claims = user.value?.idTokenClaims;
|
|
78
|
+
if (!claims) return false;
|
|
79
|
+
const roles = claims.roles ?? claims.extension_Roles ?? [];
|
|
80
|
+
return Array.isArray(roles) && roles.includes(role);
|
|
56
81
|
}
|
|
57
82
|
return {
|
|
58
83
|
// State (readonly refs)
|
|
59
|
-
isAuthenticated
|
|
60
|
-
isLoading
|
|
61
|
-
user
|
|
62
|
-
token
|
|
63
|
-
error
|
|
64
|
-
activePolicy
|
|
65
|
-
authReady
|
|
66
|
-
userName
|
|
84
|
+
isAuthenticated,
|
|
85
|
+
isLoading,
|
|
86
|
+
user,
|
|
87
|
+
token,
|
|
88
|
+
error,
|
|
89
|
+
activePolicy,
|
|
90
|
+
authReady,
|
|
91
|
+
userName,
|
|
67
92
|
// Actions
|
|
68
|
-
signIn
|
|
69
|
-
signInCityEmployee
|
|
70
|
-
signOut
|
|
71
|
-
forgotPassword
|
|
72
|
-
acquireToken
|
|
93
|
+
signIn,
|
|
94
|
+
signInCityEmployee,
|
|
95
|
+
signOut,
|
|
96
|
+
forgotPassword,
|
|
97
|
+
acquireToken,
|
|
73
98
|
// Utilities
|
|
74
|
-
hasRole
|
|
99
|
+
hasRole
|
|
75
100
|
};
|
|
76
101
|
}
|
|
77
|
-
function
|
|
102
|
+
function createB2CPlugin(options = {}) {
|
|
78
103
|
const {
|
|
79
|
-
signInPolicy
|
|
80
|
-
resetPasswordPolicy
|
|
81
|
-
debug
|
|
82
|
-
} =
|
|
83
|
-
return
|
|
104
|
+
signInPolicy = "B2C_1A_AD_SIGNIN_ONLY",
|
|
105
|
+
resetPasswordPolicy = "B2C_1A_PASSWORDRESET",
|
|
106
|
+
debug = false
|
|
107
|
+
} = options;
|
|
108
|
+
return createSSOPlugin({
|
|
84
109
|
clientConfig: {
|
|
85
|
-
provider: new
|
|
110
|
+
provider: new B2CProvider({
|
|
86
111
|
clientId: void 0,
|
|
87
112
|
b2cEnvironment: void 0,
|
|
88
113
|
authorityDomain: void 0,
|
|
89
114
|
redirectUri: void 0,
|
|
90
115
|
policies: {
|
|
91
|
-
signUpSignIn:
|
|
92
|
-
signInOnly:
|
|
93
|
-
resetPassword:
|
|
116
|
+
signUpSignIn: signInPolicy,
|
|
117
|
+
signInOnly: signInPolicy,
|
|
118
|
+
resetPassword: resetPasswordPolicy
|
|
94
119
|
}
|
|
95
120
|
}),
|
|
96
|
-
debug
|
|
121
|
+
debug
|
|
97
122
|
}
|
|
98
123
|
});
|
|
99
124
|
}
|
|
100
125
|
export {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
126
|
+
SSO_CLIENT_KEY,
|
|
127
|
+
createB2CPlugin,
|
|
128
|
+
createSSOPlugin,
|
|
129
|
+
useAuth,
|
|
130
|
+
useSSOClient,
|
|
131
|
+
useSSOStore
|
|
107
132
|
};
|
|
108
133
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../src/plugin.ts","../src/composables/useSSOClient.ts","../src/composables/useAuth.ts","../src/createB2CPlugin.ts"],"sourcesContent":["import { ref } from \"vue\";\nimport { defineStore } from \"pinia\";\nimport { SSOClient } from \"@phila/sso-core\";\nimport type { App } from \"vue\";\nimport type { SSOClientConfig, SSOClientState, AuthResponse } from \"@phila/sso-core\";\n\nexport const SSO_CLIENT_KEY = Symbol(\"sso-client\");\n\nexport interface SSOPluginOptions {\n clientConfig: SSOClientConfig;\n autoInitialize?: boolean;\n}\n\nexport const useSSOStore = defineStore(\"sso\", () => {\n const isAuthenticated = ref(false);\n const isLoading = ref(false);\n const user = ref<SSOClientState[\"user\"]>(null);\n const token = ref<string | null>(null);\n const error = ref<Error | null>(null);\n const activePolicy = ref<string | null>(null);\n const authReady = ref(false);\n\n function syncState(state: SSOClientState) {\n isAuthenticated.value = state.isAuthenticated;\n isLoading.value = state.isLoading;\n user.value = state.user;\n token.value = state.token;\n error.value = state.error;\n activePolicy.value = state.activePolicy;\n authReady.value = state.authReady;\n }\n\n return { isAuthenticated, isLoading, user, token, error, activePolicy, authReady, syncState };\n});\n\nexport function createSSOPlugin(options: SSOPluginOptions) {\n return {\n install(app: App) {\n const client = new SSOClient(options.clientConfig);\n\n // Provide the client for useSSOClient()\n app.provide(SSO_CLIENT_KEY, client);\n\n // Subscribe to state changes and sync to Pinia store\n client.events.on(\"auth:stateChanged\", (state: SSOClientState) => {\n const store = useSSOStore();\n store.syncState(state);\n });\n\n // Auto-initialize unless explicitly disabled\n if (options.autoInitialize !== false) {\n client.initialize().then((response: AuthResponse | null) => {\n if (response && options.clientConfig.debug) {\n console.log(\"[sso-vue] Auto-initialized with response:\", response);\n }\n });\n }\n },\n };\n}\n","import { inject } from \"vue\";\nimport { SSOClient } from \"@phila/sso-core\";\nimport { SSO_CLIENT_KEY } from \"../plugin\";\n\n/**\n * Access the raw SSOClient instance.\n * Must be called within a component that is a descendant of createSSOPlugin.\n */\nexport function useSSOClient(): SSOClient {\n const client = inject<SSOClient>(SSO_CLIENT_KEY);\n if (!client) {\n throw new Error(\"SSOClient not found. Did you install createSSOPlugin?\");\n }\n return client;\n}\n","import { computed } from \"vue\";\nimport { storeToRefs } from \"pinia\";\nimport { useSSOStore } from \"../plugin\";\nimport { useSSOClient } from \"./useSSOClient\";\nimport type { SignInOptions, SignOutOptions, TokenOptions } from \"@phila/sso-core\";\n\n/**\n * Primary composable for SSO authentication in Vue 3 apps.\n * API shape is intentionally similar to service-cat's existing useAuth()\n * to minimize migration friction.\n */\nexport function useAuth() {\n const store = useSSOStore();\n const client = useSSOClient();\n const { isAuthenticated, isLoading, user, token, error, activePolicy, authReady } = storeToRefs(store);\n\n // Computed helpers\n const userName = computed(() => {\n const claims = user.value?.idTokenClaims as Record<string, unknown> | undefined;\n if (!claims) return null;\n const given = (claims.given_name ?? claims.name ?? \"\") as string;\n const family = (claims.family_name ?? \"\") as string;\n return family ? `${given} ${family}`.trim() : given;\n });\n\n // Actions\n async function signIn(options?: SignInOptions): Promise<void> {\n await client.signIn(options);\n }\n\n async function signInCityEmployee(options?: SignInOptions): Promise<void> {\n await client.signInCityEmployee(options);\n }\n\n async function signOut(options?: SignOutOptions): Promise<void> {\n await client.signOut(options);\n }\n\n async function forgotPassword(): Promise<void> {\n await client.forgotPassword();\n }\n\n async function acquireToken(options?: TokenOptions): Promise<string | null> {\n return client.acquireToken(options);\n }\n\n // Utilities\n function hasRole(role: string): boolean {\n const claims = user.value?.idTokenClaims as Record<string, unknown> | undefined;\n if (!claims) return false;\n\n const roles = (claims.roles ?? claims.extension_Roles ?? []) as string[];\n return Array.isArray(roles) && roles.includes(role);\n }\n\n return {\n // State (readonly refs)\n isAuthenticated,\n isLoading,\n user,\n token,\n error,\n activePolicy,\n authReady,\n userName,\n\n // Actions\n signIn,\n signInCityEmployee,\n signOut,\n forgotPassword,\n acquireToken,\n\n // Utilities\n hasRole,\n };\n}\n","// ABOUTME: Convenience factory for vanilla Vue/Vite apps using Azure AD B2C\n// ABOUTME: Reads VITE_SSO_* env vars by convention so apps need zero B2CProvider boilerplate\nimport { B2CProvider } from \"@phila/sso-core\";\nimport { createSSOPlugin } from \"./plugin\";\n\nexport interface B2CPluginOptions {\n signInPolicy?: string;\n resetPasswordPolicy?: string;\n debug?: boolean;\n}\n\n/**\n * Creates a ready-to-use Vue plugin for Azure AD B2C authentication.\n * Reads connection details from VITE_SSO_* environment variables:\n * VITE_SSO_CLIENT_ID, VITE_SSO_TENANT, VITE_SSO_AUTHORITY_DOMAIN, VITE_SSO_REDIRECT_URI\n */\nexport function createB2CPlugin(options: B2CPluginOptions = {}) {\n const {\n signInPolicy = \"B2C_1A_AD_SIGNIN_ONLY\",\n resetPasswordPolicy = \"B2C_1A_PASSWORDRESET\",\n debug = import.meta.env.DEV,\n } = options;\n\n return createSSOPlugin({\n clientConfig: {\n provider: new B2CProvider({\n clientId: import.meta.env.VITE_SSO_CLIENT_ID,\n b2cEnvironment: import.meta.env.VITE_SSO_TENANT,\n authorityDomain: import.meta.env.VITE_SSO_AUTHORITY_DOMAIN,\n redirectUri: import.meta.env.VITE_SSO_REDIRECT_URI,\n policies: {\n signUpSignIn: signInPolicy,\n signInOnly: signInPolicy,\n resetPassword: resetPasswordPolicy,\n },\n }),\n debug,\n },\n });\n}\n"],"names":[
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../src/plugin.ts","../src/composables/useSSOClient.ts","../src/composables/useAuth.ts","../src/createB2CPlugin.ts"],"sourcesContent":["import { ref } from \"vue\";\nimport { defineStore } from \"pinia\";\nimport { SSOClient } from \"@phila/sso-core\";\nimport type { App } from \"vue\";\nimport type { SSOClientConfig, SSOClientState, AuthResponse } from \"@phila/sso-core\";\n\nexport const SSO_CLIENT_KEY = Symbol(\"sso-client\");\n\nexport interface SSOPluginOptions {\n clientConfig: SSOClientConfig;\n autoInitialize?: boolean;\n}\n\nexport const useSSOStore = defineStore(\"sso\", () => {\n const isAuthenticated = ref(false);\n const isLoading = ref(false);\n const user = ref<SSOClientState[\"user\"]>(null);\n const token = ref<string | null>(null);\n const error = ref<Error | null>(null);\n const activePolicy = ref<string | null>(null);\n const authReady = ref(false);\n\n function syncState(state: SSOClientState) {\n isAuthenticated.value = state.isAuthenticated;\n isLoading.value = state.isLoading;\n user.value = state.user;\n token.value = state.token;\n error.value = state.error;\n activePolicy.value = state.activePolicy;\n authReady.value = state.authReady;\n }\n\n return { isAuthenticated, isLoading, user, token, error, activePolicy, authReady, syncState };\n});\n\nexport function createSSOPlugin(options: SSOPluginOptions) {\n return {\n install(app: App) {\n const client = new SSOClient(options.clientConfig);\n\n // Provide the client for useSSOClient()\n app.provide(SSO_CLIENT_KEY, client);\n\n // Subscribe to state changes and sync to Pinia store\n client.events.on(\"auth:stateChanged\", (state: SSOClientState) => {\n const store = useSSOStore();\n store.syncState(state);\n });\n\n // Auto-initialize unless explicitly disabled\n if (options.autoInitialize !== false) {\n client.initialize().then((response: AuthResponse | null) => {\n if (response && options.clientConfig.debug) {\n console.log(\"[sso-vue] Auto-initialized with response:\", response);\n }\n });\n }\n },\n };\n}\n","import { inject } from \"vue\";\nimport { SSOClient } from \"@phila/sso-core\";\nimport { SSO_CLIENT_KEY } from \"../plugin\";\n\n/**\n * Access the raw SSOClient instance.\n * Must be called within a component that is a descendant of createSSOPlugin.\n */\nexport function useSSOClient(): SSOClient {\n const client = inject<SSOClient>(SSO_CLIENT_KEY);\n if (!client) {\n throw new Error(\"SSOClient not found. Did you install createSSOPlugin?\");\n }\n return client;\n}\n","import { computed } from \"vue\";\nimport { storeToRefs } from \"pinia\";\nimport { useSSOStore } from \"../plugin\";\nimport { useSSOClient } from \"./useSSOClient\";\nimport type { SignInOptions, SignOutOptions, TokenOptions } from \"@phila/sso-core\";\n\n/**\n * Primary composable for SSO authentication in Vue 3 apps.\n * API shape is intentionally similar to service-cat's existing useAuth()\n * to minimize migration friction.\n */\nexport function useAuth() {\n const store = useSSOStore();\n const client = useSSOClient();\n const { isAuthenticated, isLoading, user, token, error, activePolicy, authReady } = storeToRefs(store);\n\n // Computed helpers\n const userName = computed(() => {\n const claims = user.value?.idTokenClaims as Record<string, unknown> | undefined;\n if (!claims) return null;\n const given = (claims.given_name ?? claims.name ?? \"\") as string;\n const family = (claims.family_name ?? \"\") as string;\n return family ? `${given} ${family}`.trim() : given;\n });\n\n // Actions\n async function signIn(options?: SignInOptions): Promise<void> {\n await client.signIn(options);\n }\n\n async function signInCityEmployee(options?: SignInOptions): Promise<void> {\n await client.signInCityEmployee(options);\n }\n\n async function signOut(options?: SignOutOptions): Promise<void> {\n await client.signOut(options);\n }\n\n async function forgotPassword(): Promise<void> {\n await client.forgotPassword();\n }\n\n async function acquireToken(options?: TokenOptions): Promise<string | null> {\n return client.acquireToken(options);\n }\n\n // Utilities\n function hasRole(role: string): boolean {\n const claims = user.value?.idTokenClaims as Record<string, unknown> | undefined;\n if (!claims) return false;\n\n const roles = (claims.roles ?? claims.extension_Roles ?? []) as string[];\n return Array.isArray(roles) && roles.includes(role);\n }\n\n return {\n // State (readonly refs)\n isAuthenticated,\n isLoading,\n user,\n token,\n error,\n activePolicy,\n authReady,\n userName,\n\n // Actions\n signIn,\n signInCityEmployee,\n signOut,\n forgotPassword,\n acquireToken,\n\n // Utilities\n hasRole,\n };\n}\n","// ABOUTME: Convenience factory for vanilla Vue/Vite apps using Azure AD B2C\n// ABOUTME: Reads VITE_SSO_* env vars by convention so apps need zero B2CProvider boilerplate\nimport { B2CProvider } from \"@phila/sso-core\";\nimport { createSSOPlugin } from \"./plugin\";\n\nexport interface B2CPluginOptions {\n signInPolicy?: string;\n resetPasswordPolicy?: string;\n debug?: boolean;\n}\n\n/**\n * Creates a ready-to-use Vue plugin for Azure AD B2C authentication.\n * Reads connection details from VITE_SSO_* environment variables:\n * VITE_SSO_CLIENT_ID, VITE_SSO_TENANT, VITE_SSO_AUTHORITY_DOMAIN, VITE_SSO_REDIRECT_URI\n */\nexport function createB2CPlugin(options: B2CPluginOptions = {}) {\n const {\n signInPolicy = \"B2C_1A_AD_SIGNIN_ONLY\",\n resetPasswordPolicy = \"B2C_1A_PASSWORDRESET\",\n debug = import.meta.env.DEV,\n } = options;\n\n return createSSOPlugin({\n clientConfig: {\n provider: new B2CProvider({\n clientId: import.meta.env.VITE_SSO_CLIENT_ID,\n b2cEnvironment: import.meta.env.VITE_SSO_TENANT,\n authorityDomain: import.meta.env.VITE_SSO_AUTHORITY_DOMAIN,\n redirectUri: import.meta.env.VITE_SSO_REDIRECT_URI,\n policies: {\n signUpSignIn: signInPolicy,\n signInOnly: signInPolicy,\n resetPassword: resetPasswordPolicy,\n },\n }),\n debug,\n },\n });\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phila/sso-vue",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Vue 3 adapter for @phila/sso-core",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"author": "City of Philadelphia",
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@phila/sso-core": "0.0.
|
|
29
|
+
"@phila/sso-core": "0.0.4"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"vue": "^3.0.0",
|