@cemiar/auth-sdk 1.0.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,122 @@
1
+ # @cemiar/auth-sdk
2
+
3
+ Reusable helpers for integrating Cemiar Auth in web front-ends and Node.js APIs.
4
+
5
+ This package bundles:
6
+
7
+ - A browser-friendly Cemiar Auth client with email codes, Microsoft login support, token storage, refresh and logout helpers
8
+ - Axios utilities to attach automatic Authorization headers and refresh tokens on 401 responses
9
+ - A Hapi JWT strategy helper that fetches Cemiar Auth signing keys from JWKS and validates incoming requests
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @cemiar/auth-sdk
15
+ ```
16
+
17
+ When working inside this monorepo, you can add it via a file reference:
18
+
19
+ ```bash
20
+ npm install @cemiar/auth-sdk@file:../packages/cemiar-auth-sdk
21
+ ```
22
+
23
+ Make sure the host project also has the required peer dependencies installed (`@hapi/hapi`, `hapi-auth-jwt2`).
24
+
25
+ ## Usage
26
+
27
+ ### Front-end (Vue/React/etc.)
28
+
29
+ ```ts
30
+ import { createCemiarAuthClient } from "@cemiar/auth-sdk";
31
+
32
+ const authClient = createCemiarAuthClient({
33
+ baseUrl: import.meta.env.VITE_CEMIAR_AUTH_URL!,
34
+ tenantId: import.meta.env.VITE_CEMIAR_TENANT_ID!,
35
+ auds: (import.meta.env.VITE_CEMIAR_AUDS || "").split(","),
36
+ redirectUrl: `${window.location.origin}/auth/microsoft/callback`,
37
+ logoutRedirectUrl: `${window.location.origin}/login`
38
+ });
39
+
40
+ // Trigger Microsoft login
41
+ window.location.href = authClient.getMicrosoftLoginUrl();
42
+
43
+ // Email code flow
44
+ await authClient.sendEmailCode("user@example.com");
45
+ const { accessToken } = await authClient.verifyEmailCode("user@example.com", "123456");
46
+
47
+ // Axios client with automatic Authorization header and refresh-on-401
48
+ const api = authClient.createApiClient({ baseURL: import.meta.env.VITE_API_BASE });
49
+ const jobs = await api.get("/api/jobs");
50
+
51
+ // Manual interceptor attachment if you already have an axios instance
52
+ authClient.attachInterceptors(existingAxiosInstance);
53
+ ```
54
+
55
+ #### Token storage hooks
56
+
57
+ By default the client stores tokens in `localStorage`. Override storage to customize behaviour:
58
+
59
+ ```ts
60
+ import { createCemiarAuthClient, TokenStorage } from "@cemiar/auth-sdk";
61
+
62
+ const storage: TokenStorage = {
63
+ getToken: () => cookies.get("cemiar-access"),
64
+ setToken: token => {
65
+ token ? cookies.set("cemiar-access", token) : cookies.remove("cemiar-access");
66
+ }
67
+ };
68
+
69
+ const authClient = createCemiarAuthClient({
70
+ baseUrl: import.meta.env.VITE_CEMIAR_AUTH_URL!,
71
+ tenantId: import.meta.env.VITE_CEMIAR_TENANT_ID!,
72
+ auds: ["cemiar-jobs"],
73
+ storage
74
+ });
75
+ ```
76
+
77
+ ### Backend (Hapi)
78
+
79
+ ```ts
80
+ import { registerCemiarAuthHapi } from "@cemiar/auth-sdk";
81
+
82
+ await registerCemiarAuthHapi(server, {
83
+ authUrl: process.env.CEMIAR_AUTH_URL!,
84
+ strategyName: "jwt",
85
+ validate: async decoded => ({
86
+ isValid: true,
87
+ credentials: decoded.payload
88
+ })
89
+ });
90
+ ```
91
+
92
+ The helper automatically fetches signing keys from Cemiar Auth JWKS endpoint and keeps them cached.
93
+
94
+ To customise the cache and algorithms:
95
+
96
+ ```ts
97
+ await registerCemiarAuthHapi(server, {
98
+ authUrl: process.env.CEMIAR_AUTH_URL!,
99
+ algorithms: ["RS256"],
100
+ cacheMaxAgeMs: 12 * 60 * 60 * 1000
101
+ });
102
+ ```
103
+
104
+ ### Environment Variables
105
+
106
+ | Variable | Description |
107
+ | -------------------------- | ------------------------------------------------------------------ |
108
+ | `VITE_CEMIAR_AUTH_URL` | Base Cemiar Auth URL (e.g. `https://cemiar-auth.example.com/auth`) |
109
+ | `VITE_CEMIAR_TENANT_ID` | Tenant identifier provided by Cemiar Auth |
110
+ | `VITE_CEMIAR_AUDS` | Comma separated list of audiences |
111
+ | `VITE_CEMIAR_REDIRECT_URL` | Optional override for Microsoft redirect |
112
+ | `CEMIAR_AUTH_URL` | Backend URL for Cemiar Auth (no Vite prefix) |
113
+
114
+ ### Building
115
+
116
+ ```bash
117
+ cd packages/cemiar-auth-sdk
118
+ npm install
119
+ npm run build
120
+ ```
121
+
122
+ Output will be generated under `dist/`.
@@ -0,0 +1,30 @@
1
+ import { AxiosInstance } from "axios";
2
+ import type { AuthTokens, CemiarAuthClientConfig, CemiarAuthClientInstance, CreateApiClientOptions, EmailCodeResponse, LogoutOptions, VerifyEmailResult } from "../shared/Types.js";
3
+ export declare class CemiarAuthClient implements CemiarAuthClientInstance {
4
+ private readonly baseUrl;
5
+ private readonly tenantId;
6
+ private readonly auds;
7
+ private readonly redirectUrl?;
8
+ private storage;
9
+ private readonly onTokenChange?;
10
+ private readonly onAuthFailure?;
11
+ private readonly logoutRedirectUrl?;
12
+ private refreshPromise;
13
+ constructor(config: CemiarAuthClientConfig);
14
+ getAccessToken(): string | null;
15
+ setAccessToken(token: string | null): void;
16
+ private authPost;
17
+ sendEmailCode(email: string): Promise<EmailCodeResponse>;
18
+ verifyEmailCode(email: string, code: string): Promise<VerifyEmailResult>;
19
+ getMicrosoftLoginUrl(extraParams?: Record<string, string>): string;
20
+ exchangeMicrosoftCode(code: string): Promise<AuthTokens>;
21
+ refreshToken(): Promise<AuthTokens>;
22
+ logout(options?: LogoutOptions): Promise<void>;
23
+ createApiClient(options: CreateApiClientOptions): AxiosInstance;
24
+ attachInterceptors(instance: AxiosInstance): AxiosInstance;
25
+ private addAuthHeader;
26
+ private handleResponseError;
27
+ private queueTokenRefresh;
28
+ private handleAuthFailure;
29
+ }
30
+ export declare function createCemiarAuthClient(config: CemiarAuthClientConfig): CemiarAuthClientInstance;
@@ -0,0 +1,171 @@
1
+ import axios from "axios";
2
+ import { createDefaultTokenStorage } from "./Storage.js";
3
+ import { normalizeBaseUrl, isBrowser } from "../shared/Utils.js";
4
+ function parseAuds(auds) {
5
+ return auds.map(a => a.trim()).filter(Boolean);
6
+ }
7
+ function extractAccessToken(data) {
8
+ var _a;
9
+ const token = (_a = data.accessToken) !== null && _a !== void 0 ? _a : data.token;
10
+ if (typeof token === "string" && token.length > 0) {
11
+ return token;
12
+ }
13
+ throw new Error("Cemiar Auth response missing access token");
14
+ }
15
+ export class CemiarAuthClient {
16
+ constructor(config) {
17
+ var _a, _b, _c;
18
+ this.storage = createDefaultTokenStorage();
19
+ this.refreshPromise = null;
20
+ this.baseUrl = normalizeBaseUrl(config.baseUrl || "http://localhost:3000/auth");
21
+ this.tenantId = config.tenantId;
22
+ this.auds = parseAuds(config.auds);
23
+ this.redirectUrl =
24
+ (_a = config.redirectUrl) !== null && _a !== void 0 ? _a : (isBrowser ? `${window.location.origin}/auth/microsoft/callback` : undefined);
25
+ this.onTokenChange = config.onTokenChange;
26
+ this.onAuthFailure = config.onAuthFailure;
27
+ this.logoutRedirectUrl =
28
+ (_b = config.logoutRedirectUrl) !== null && _b !== void 0 ? _b : (isBrowser ? `${window.location.origin}/login` : undefined);
29
+ this.storage = (_c = config.storage) !== null && _c !== void 0 ? _c : createDefaultTokenStorage();
30
+ }
31
+ getAccessToken() {
32
+ return this.storage.getToken();
33
+ }
34
+ setAccessToken(token) {
35
+ var _a;
36
+ this.storage.setToken(token);
37
+ (_a = this.onTokenChange) === null || _a === void 0 ? void 0 : _a.call(this, token);
38
+ }
39
+ async authPost(path, body, withCredentials = true) {
40
+ const url = `${this.baseUrl}${path}`;
41
+ const response = await axios.post(url, body, {
42
+ withCredentials
43
+ });
44
+ return response.data;
45
+ }
46
+ async sendEmailCode(email) {
47
+ return this.authPost("/emailCodes/generate", { email });
48
+ }
49
+ async verifyEmailCode(email, code) {
50
+ const payload = {
51
+ email,
52
+ code,
53
+ tenantId: this.tenantId,
54
+ auds: this.auds
55
+ };
56
+ const data = await this.authPost("/emailCodes/validate", payload);
57
+ const accessToken = extractAccessToken(data);
58
+ this.setAccessToken(accessToken);
59
+ return {
60
+ accessToken,
61
+ authenticated: Boolean(data.authenticated),
62
+ blacklisted: Boolean(data.blacklisted),
63
+ raw: data
64
+ };
65
+ }
66
+ getMicrosoftLoginUrl(extraParams = {}) {
67
+ const params = new URLSearchParams({
68
+ tenantId: this.tenantId,
69
+ auds: this.auds.join(","),
70
+ ...(this.redirectUrl ? { redirectUrl: this.redirectUrl } : {})
71
+ });
72
+ for (const [key, value] of Object.entries(extraParams)) {
73
+ params.set(key, value);
74
+ }
75
+ return `${this.baseUrl}/microsoft?${params.toString()}`;
76
+ }
77
+ async exchangeMicrosoftCode(code) {
78
+ const payload = {
79
+ code,
80
+ ...(this.redirectUrl ? { redirectUrl: this.redirectUrl } : {})
81
+ };
82
+ const data = await this.authPost("/microsoft/callback", payload);
83
+ const accessToken = extractAccessToken(data);
84
+ this.setAccessToken(accessToken);
85
+ return { accessToken };
86
+ }
87
+ async refreshToken() {
88
+ const data = await this.authPost("/refresh", {}, true);
89
+ const accessToken = extractAccessToken(data);
90
+ this.setAccessToken(accessToken);
91
+ return { accessToken };
92
+ }
93
+ async logout(options = {}) {
94
+ if (options.clearToken !== false) {
95
+ this.setAccessToken(null);
96
+ }
97
+ const redirectUrl = options.redirectUrl || this.logoutRedirectUrl;
98
+ const logoutUrl = redirectUrl
99
+ ? `${this.baseUrl}/microsoft/logout?redirectUrl=${encodeURIComponent(redirectUrl)}`
100
+ : `${this.baseUrl}/logout`;
101
+ try {
102
+ if (options.performRedirect === false || !isBrowser) {
103
+ await axios.post(`${this.baseUrl}/logout`, {}, { withCredentials: true });
104
+ return;
105
+ }
106
+ window.location.href = logoutUrl;
107
+ }
108
+ catch {
109
+ // ignore network errors during logout
110
+ }
111
+ }
112
+ createApiClient(options) {
113
+ var _a;
114
+ const instance = axios.create({
115
+ baseURL: options.baseURL,
116
+ withCredentials: true,
117
+ ...((_a = options.axiosConfig) !== null && _a !== void 0 ? _a : {})
118
+ });
119
+ return this.attachInterceptors(instance);
120
+ }
121
+ attachInterceptors(instance) {
122
+ instance.interceptors.request.use(config => this.addAuthHeader(config));
123
+ instance.interceptors.response.use(response => response, error => this.handleResponseError(error, instance));
124
+ return instance;
125
+ }
126
+ addAuthHeader(config) {
127
+ const token = this.getAccessToken();
128
+ if (token && config.headers) {
129
+ config.headers.Authorization = `Bearer ${token}`;
130
+ }
131
+ return config;
132
+ }
133
+ async handleResponseError(error, instance) {
134
+ var _a;
135
+ const status = (_a = error.response) === null || _a === void 0 ? void 0 : _a.status;
136
+ const originalRequest = error.config;
137
+ if (status !== 401 || !originalRequest || originalRequest._retry) {
138
+ return Promise.reject(error);
139
+ }
140
+ originalRequest._retry = true;
141
+ try {
142
+ const newToken = await this.queueTokenRefresh();
143
+ if (originalRequest.headers) {
144
+ originalRequest.headers.Authorization = `Bearer ${newToken}`;
145
+ }
146
+ return instance(originalRequest);
147
+ }
148
+ catch (refreshError) {
149
+ this.handleAuthFailure();
150
+ return Promise.reject(refreshError);
151
+ }
152
+ }
153
+ async queueTokenRefresh() {
154
+ if (!this.refreshPromise) {
155
+ this.refreshPromise = this.refreshToken()
156
+ .then(tokens => tokens.accessToken)
157
+ .finally(() => {
158
+ this.refreshPromise = null;
159
+ });
160
+ }
161
+ return this.refreshPromise;
162
+ }
163
+ handleAuthFailure() {
164
+ var _a;
165
+ this.setAccessToken(null);
166
+ (_a = this.onAuthFailure) === null || _a === void 0 ? void 0 : _a.call(this);
167
+ }
168
+ }
169
+ export function createCemiarAuthClient(config) {
170
+ return new CemiarAuthClient(config);
171
+ }
@@ -0,0 +1,4 @@
1
+ import type { TokenStorage } from "../shared/Types.js";
2
+ export declare function createMemoryStorage(initialValue?: string | null): TokenStorage;
3
+ export declare function createLocalStorageWrapper(key?: string): TokenStorage;
4
+ export declare function createDefaultTokenStorage(): TokenStorage;
@@ -0,0 +1,42 @@
1
+ import { isBrowser } from "../shared/Utils.js";
2
+ export function createMemoryStorage(initialValue = null) {
3
+ let token = initialValue;
4
+ return {
5
+ getToken: () => token,
6
+ setToken: value => {
7
+ token = value;
8
+ }
9
+ };
10
+ }
11
+ export function createLocalStorageWrapper(key = "accessToken") {
12
+ if (!isBrowser || !("localStorage" in globalThis)) {
13
+ return createMemoryStorage(null);
14
+ }
15
+ return {
16
+ getToken: () => {
17
+ try {
18
+ return window.localStorage.getItem(key);
19
+ }
20
+ catch (error) {
21
+ console.warn("Failed to read token from localStorage", error);
22
+ return null;
23
+ }
24
+ },
25
+ setToken: value => {
26
+ try {
27
+ if (value) {
28
+ window.localStorage.setItem(key, value);
29
+ }
30
+ else {
31
+ window.localStorage.removeItem(key);
32
+ }
33
+ }
34
+ catch (error) {
35
+ console.warn("Failed to write token to localStorage", error);
36
+ }
37
+ }
38
+ };
39
+ }
40
+ export function createDefaultTokenStorage() {
41
+ return createLocalStorageWrapper();
42
+ }
@@ -0,0 +1,2 @@
1
+ export { CemiarAuthClient, createCemiarAuthClient } from "./browser/CemiarAuthClient.js";
2
+ export * from "./shared/Types.js";
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { CemiarAuthClient, createCemiarAuthClient } from "./browser/CemiarAuthClient.js";
2
+ export * from "./shared/Types.js";
@@ -0,0 +1,8 @@
1
+ import type { HapiJwtStrategyOptions, JwtParts } from "../shared/Types.js";
2
+ import type { Server } from "@hapi/hapi";
3
+ declare function createSigningKeyResolver(jwksUri: string, cacheMaxAgeMs: number): {
4
+ getKey: (decoded: JwtParts) => Promise<string>;
5
+ };
6
+ export declare function registerCemiarAuthHapi(server: Server, options: HapiJwtStrategyOptions): Promise<void>;
7
+ export { createSigningKeyResolver };
8
+ export type { JwtValidateFn, JwtValidateResult, JwtParts } from "../shared/Types.js";
@@ -0,0 +1,59 @@
1
+ import jwksClient from "jwks-rsa";
2
+ import { normalizeBaseUrl } from "../shared/Utils.js";
3
+ const DEFAULT_ALGORITHMS = ["RS256"];
4
+ const DEFAULT_CACHE_MAX_AGE = 24 * 60 * 60 * 1000; // 24 hours
5
+ const defaultValidate = async (decoded) => ({
6
+ isValid: true,
7
+ credentials: decoded.payload
8
+ });
9
+ function createSigningKeyResolver(jwksUri, cacheMaxAgeMs) {
10
+ const client = jwksClient({
11
+ jwksUri,
12
+ cache: true,
13
+ cacheMaxEntries: 5,
14
+ cacheMaxAge: cacheMaxAgeMs
15
+ });
16
+ const getKey = async (decoded) => {
17
+ var _a;
18
+ const kid = (_a = decoded.header) === null || _a === void 0 ? void 0 : _a.kid;
19
+ if (!kid) {
20
+ throw new Error("JWT header is missing 'kid' claim");
21
+ }
22
+ return new Promise((resolve, reject) => {
23
+ client.getSigningKey(kid, (error, key) => {
24
+ if (error) {
25
+ return reject(error);
26
+ }
27
+ if (!key) {
28
+ return reject(new Error(`Unable to resolve signing key for kid ${kid}`));
29
+ }
30
+ resolve(key.getPublicKey());
31
+ });
32
+ });
33
+ };
34
+ return { getKey };
35
+ }
36
+ export async function registerCemiarAuthHapi(server, options) {
37
+ var _a;
38
+ const { authUrl, strategyName = "cemiar-jwt", algorithms = DEFAULT_ALGORITHMS, cacheMaxAgeMs = DEFAULT_CACHE_MAX_AGE, validate = defaultValidate } = options;
39
+ const jwksUri = `${normalizeBaseUrl(authUrl)}/.well-known/jwks.json`;
40
+ const { getKey } = createSigningKeyResolver(jwksUri, cacheMaxAgeMs);
41
+ const pluginModule = await import("hapi-auth-jwt2");
42
+ const plugin = ((_a = pluginModule.default) !== null && _a !== void 0 ? _a : pluginModule);
43
+ await server.register(plugin);
44
+ server.auth.strategy(strategyName, "jwt", {
45
+ key: async (decoded) => {
46
+ const key = await getKey(decoded);
47
+ return { key };
48
+ },
49
+ validate: async (decoded, request, h) => {
50
+ return validate(decoded, request, h);
51
+ },
52
+ verifyOptions: {
53
+ algorithms
54
+ },
55
+ complete: true
56
+ });
57
+ server.auth.default(strategyName);
58
+ }
59
+ export { createSigningKeyResolver };
@@ -0,0 +1,2 @@
1
+ export { registerCemiarAuthHapi, createSigningKeyResolver } from "./ServerHandler.js";
2
+ export type { JwtValidateFn, JwtValidateResult, JwtParts, HapiJwtStrategyOptions } from "../shared/Types.js";
@@ -0,0 +1 @@
1
+ export { registerCemiarAuthHapi, createSigningKeyResolver } from "./ServerHandler.js";
@@ -0,0 +1,81 @@
1
+ import type { AxiosInstance, AxiosRequestConfig } from "axios";
2
+ import type { Request, ResponseToolkit } from "@hapi/hapi";
3
+ export interface TokenStorage {
4
+ getToken(): string | null;
5
+ setToken(token: string | null): void;
6
+ }
7
+ export interface CemiarAuthClientConfig {
8
+ baseUrl: string;
9
+ tenantId: string;
10
+ auds: string[];
11
+ redirectUrl?: string;
12
+ storage?: TokenStorage;
13
+ onTokenChange?: (token: string | null) => void;
14
+ onAuthFailure?: () => void;
15
+ logoutRedirectUrl?: string;
16
+ }
17
+ export interface AuthTokens {
18
+ accessToken: string;
19
+ }
20
+ export interface EmailCodeResponse {
21
+ success: boolean;
22
+ [key: string]: unknown;
23
+ }
24
+ export interface EmailVerifyResponse {
25
+ token: string;
26
+ authenticated: boolean;
27
+ blacklisted: boolean;
28
+ [key: string]: unknown;
29
+ }
30
+ export interface VerifyEmailResult extends AuthTokens {
31
+ authenticated: boolean;
32
+ blacklisted: boolean;
33
+ raw: EmailVerifyResponse;
34
+ }
35
+ export interface RefreshResponse {
36
+ accessToken?: string;
37
+ token?: string;
38
+ [key: string]: unknown;
39
+ }
40
+ export interface CreateApiClientOptions {
41
+ baseURL: string;
42
+ axiosConfig?: AxiosRequestConfig;
43
+ }
44
+ export interface LogoutOptions {
45
+ redirectUrl?: string;
46
+ performRedirect?: boolean;
47
+ clearToken?: boolean;
48
+ }
49
+ export interface JwtHeader {
50
+ kid: string;
51
+ alg: string;
52
+ }
53
+ export interface JwtParts {
54
+ header: JwtHeader;
55
+ payload: unknown;
56
+ signature?: string;
57
+ }
58
+ export interface JwtValidateResult {
59
+ isValid: boolean;
60
+ credentials?: unknown;
61
+ }
62
+ export type JwtValidateFn = (decoded: JwtParts, request: Request, h: ResponseToolkit) => Promise<JwtValidateResult> | JwtValidateResult;
63
+ export interface HapiJwtStrategyOptions {
64
+ authUrl: string;
65
+ strategyName?: string;
66
+ algorithms?: string[];
67
+ cacheMaxAgeMs?: number;
68
+ validate?: JwtValidateFn;
69
+ }
70
+ export interface CemiarAuthClientInstance {
71
+ getAccessToken(): string | null;
72
+ setAccessToken(token: string | null): void;
73
+ getMicrosoftLoginUrl(params?: Record<string, string>): string;
74
+ sendEmailCode(email: string): Promise<EmailCodeResponse>;
75
+ verifyEmailCode(email: string, code: string): Promise<VerifyEmailResult>;
76
+ exchangeMicrosoftCode(code: string): Promise<AuthTokens>;
77
+ refreshToken(): Promise<AuthTokens>;
78
+ logout(options?: LogoutOptions): Promise<void>;
79
+ createApiClient(options: CreateApiClientOptions): AxiosInstance;
80
+ attachInterceptors(instance: AxiosInstance): AxiosInstance;
81
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export declare function normalizeBaseUrl(url: string): string;
2
+ export declare const isBrowser: boolean;
@@ -0,0 +1,7 @@
1
+ export function normalizeBaseUrl(url) {
2
+ if (!url) {
3
+ return "";
4
+ }
5
+ return url.endsWith("/") ? url.slice(0, -1) : url;
6
+ }
7
+ export const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined";
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@cemiar/auth-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Cemiar Auth integration helpers for web apps and APIs.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ },
13
+ "./server": {
14
+ "import": "./dist/server/index.js",
15
+ "types": "./dist/server/index.d.ts"
16
+ },
17
+ "./package.json": "./package.json"
18
+ },
19
+ "files": [
20
+ "dist"
21
+ ],
22
+ "scripts": {
23
+ "build": "tsc -p tsconfig.json",
24
+ "clean": "rimraf dist"
25
+ },
26
+ "keywords": [
27
+ "cemiar",
28
+ "auth",
29
+ "jwt",
30
+ "hapi",
31
+ "axios"
32
+ ],
33
+ "author": "Cemiar",
34
+ "license": "MIT",
35
+ "dependencies": {
36
+ "axios": "^1.7.0",
37
+ "jwks-rsa": "^3.1.0"
38
+ },
39
+ "peerDependencies": {
40
+ "@hapi/hapi": "^21.3.0",
41
+ "hapi-auth-jwt2": "^10.5.0"
42
+ },
43
+ "devDependencies": {
44
+ "@hapi/hapi": "^21.3.0",
45
+ "@types/node": "^20.11.30",
46
+ "hapi-auth-jwt2": "^10.5.0",
47
+ "rimraf": "^5.0.5",
48
+ "typescript": "^5.3.3"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ }
53
+ }