@dehwyyy/auth 1.0.2

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,50 @@
1
+ ## TokenAttacher & TokenRefresher middleware usage
2
+
3
+ ```ts
4
+ import { GetAuthService } from "@dehwyyy/auth";
5
+ import type { Middleware } from "openapi-fetch";
6
+
7
+ export const AuthorizationHeaderAttacherMiddleware: Middleware = {
8
+ async onRequest({ request }) {
9
+ return GetAuthService(APP, BASE_URL).WithAuthorizationToken(request);
10
+ },
11
+ }
12
+
13
+ export const TokenRefresherMiddleware: Middleware = {
14
+ async onResponse({ response, request }) {
15
+ if (response.status !== 401 || request.url.includes('/auth/refresh')) {
16
+ return response;
17
+ }
18
+
19
+ return GetAuthService(APP, BASE_URL).RefreshAndRetry(request, response);
20
+ }
21
+ }
22
+
23
+
24
+ ```
25
+
26
+ ## Guard usage example
27
+
28
+ ```ts
29
+ import { Guards } from '@dehwyyy/auth/guard';
30
+ import { createRouter, createWebHistory } from 'vue-router';
31
+
32
+ enum Roles {
33
+ ADMIN = "Admin",
34
+ MANAGER = "Manager",
35
+ USER = "User"
36
+ }
37
+
38
+ const g = new Guards(APP, BASE_URL, "/login")
39
+
40
+ export const router = createRouter({
41
+ history: createWebHistory(import.meta.env.VITE_BASE_URL),
42
+ routes: [
43
+ { path: "/login", name: "Login", component: Login },
44
+ { path: "/dashboard", name: "DashboardIndex", component: View, beforeEnter: g.Auth([Roles.USER]) },
45
+ { path: "/dashboard/users", name: "DashboardUsers", component: Users, beforeEnter: g.Auth([Roles.ADMIN, Roles.MANAGER]) },
46
+ { path: "/:pathMatch(.*)*", redirect: "/login" },
47
+ ],
48
+ });
49
+
50
+ ```
@@ -0,0 +1,2 @@
1
+ import type { paths } from './schema';
2
+ export declare const getClient: (app: string, baseUrl: string) => import("openapi-fetch").Client<paths, `${string}/${string}`>;
@@ -0,0 +1,14 @@
1
+ import createClient from 'openapi-fetch';
2
+ import { Middleware } from './middleware';
3
+ let client = null;
4
+ export const getClient = (app, baseUrl) => {
5
+ if (!client) {
6
+ client = createClient({
7
+ baseUrl,
8
+ credentials: 'include',
9
+ });
10
+ const middleware = new Middleware(app, baseUrl);
11
+ client.use(middleware.AuthorizationHeaderAttacher, middleware.TokenRefresher);
12
+ }
13
+ return client;
14
+ };
@@ -0,0 +1,8 @@
1
+ import type { Middleware as OpenAPIMiddleware } from 'openapi-fetch';
2
+ export declare class Middleware {
3
+ private app;
4
+ private baseUrl;
5
+ constructor(app: string, baseUrl: string);
6
+ get AuthorizationHeaderAttacher(): OpenAPIMiddleware;
7
+ get TokenRefresher(): OpenAPIMiddleware;
8
+ }
@@ -0,0 +1,28 @@
1
+ import { GetAuthService } from '../index';
2
+ export class Middleware {
3
+ app;
4
+ baseUrl;
5
+ constructor(app, baseUrl) {
6
+ this.app = app;
7
+ this.baseUrl = baseUrl;
8
+ }
9
+ get AuthorizationHeaderAttacher() {
10
+ const t = this;
11
+ return {
12
+ async onRequest({ request }) {
13
+ return GetAuthService(t.app, t.baseUrl).WithAuthorizationToken(request);
14
+ },
15
+ };
16
+ }
17
+ get TokenRefresher() {
18
+ const t = this;
19
+ return {
20
+ async onResponse({ response, request }) {
21
+ if (response.status !== 401 || request.url.includes('/auth/refresh')) {
22
+ return response;
23
+ }
24
+ return GetAuthService(t.app, t.baseUrl).RefreshAndRetry(request, response);
25
+ },
26
+ };
27
+ }
28
+ }
@@ -0,0 +1,8 @@
1
+ export declare const enum StorageKey {
2
+ ACCESS_TOKEN = "accessToken"
3
+ }
4
+ export declare class Storage {
5
+ static Set(key: StorageKey, value: string): void;
6
+ static Delete(key: StorageKey): void;
7
+ static Get(key: StorageKey): string | null;
8
+ }
@@ -0,0 +1,15 @@
1
+ export var StorageKey;
2
+ (function (StorageKey) {
3
+ StorageKey["ACCESS_TOKEN"] = "accessToken";
4
+ })(StorageKey || (StorageKey = {}));
5
+ export class Storage {
6
+ static Set(key, value) {
7
+ localStorage.setItem(key, value);
8
+ }
9
+ static Delete(key) {
10
+ localStorage.removeItem(key);
11
+ }
12
+ static Get(key) {
13
+ return localStorage.getItem(key);
14
+ }
15
+ }
@@ -0,0 +1,14 @@
1
+ interface Route {
2
+ query: Record<string, unknown>;
3
+ fullPath: string;
4
+ }
5
+ export declare class Guards {
6
+ private app;
7
+ private apiURL;
8
+ private redirectBaseUrl;
9
+ private redirectUriPrefix;
10
+ constructor(app: string, apiURL: string, redirectBaseUrl: string, redirectUriPrefix?: string);
11
+ private getLoginRedirectPath;
12
+ Auth: (roles?: string[]) => (to: Route, from: Route) => Promise<string | true>;
13
+ }
14
+ export {};
@@ -0,0 +1,59 @@
1
+ import { Storage, StorageKey } from '../client/storage/localStorage';
2
+ import { GetAuthService } from '../index';
3
+ let meCache = null;
4
+ const ME_CACHE_TTL_MS = 15 * 1000;
5
+ function arrayIntercept(arr1, arr2) {
6
+ return arr1.filter((item) => arr2.includes(item));
7
+ }
8
+ export class Guards {
9
+ app;
10
+ apiURL;
11
+ redirectBaseUrl;
12
+ redirectUriPrefix;
13
+ constructor(app, apiURL, redirectBaseUrl, redirectUriPrefix = "") {
14
+ this.app = app;
15
+ this.apiURL = apiURL;
16
+ this.redirectBaseUrl = redirectBaseUrl;
17
+ this.redirectUriPrefix = redirectUriPrefix;
18
+ }
19
+ getLoginRedirectPath(to, from) {
20
+ const redirectUri = from.query['redirect_uri'] || encodeURIComponent(to.fullPath);
21
+ const redirectPath = `${this.redirectBaseUrl}?redirect_uri=${this.redirectUriPrefix}${redirectUri}`;
22
+ return redirectPath;
23
+ }
24
+ Auth = (roles = []) => {
25
+ return async (to, from) => {
26
+ const loginRedirect = this.getLoginRedirectPath(to, from);
27
+ const token = Storage.Get(StorageKey.ACCESS_TOKEN);
28
+ if (!token) {
29
+ if (roles.length === 0)
30
+ return true;
31
+ console.warn('Access denied');
32
+ return loginRedirect;
33
+ }
34
+ const now = Date.now();
35
+ if (meCache && meCache.expiresAt > now) {
36
+ const data = meCache.data;
37
+ if (arrayIntercept(data.roles, roles).length === 0) {
38
+ return loginRedirect;
39
+ }
40
+ return true;
41
+ }
42
+ const response = await GetAuthService(this.app, this.apiURL).Validate();
43
+ if (!response) {
44
+ console.warn('Access denied');
45
+ return loginRedirect;
46
+ }
47
+ meCache = { data: response, expiresAt: Date.now() + ME_CACHE_TTL_MS };
48
+ if (response.roles.length === 0) {
49
+ console.warn('No roles found');
50
+ return loginRedirect;
51
+ }
52
+ if (arrayIntercept(response.roles, roles).length === 0) {
53
+ console.warn('Access denied');
54
+ return loginRedirect;
55
+ }
56
+ return true;
57
+ };
58
+ };
59
+ }
@@ -0,0 +1,22 @@
1
+ interface ValidateResponse {
2
+ userId: string;
3
+ roles: string[];
4
+ }
5
+ declare class AuthService {
6
+ private app;
7
+ private apiURL;
8
+ private refreshPromise;
9
+ constructor(app: string, apiURL: string);
10
+ withApp(app: string): this;
11
+ private getClient;
12
+ /**
13
+ * @description Refresh access token
14
+ * @returns `accessToken` -> refresh ok. `null` -> refresh failed
15
+ **/
16
+ private doRefresh;
17
+ Validate(): Promise<ValidateResponse | null>;
18
+ WithAuthorizationToken(request: Request, token?: string | null): Request;
19
+ RefreshAndRetry(request: Request, response: Response): Promise<Response>;
20
+ }
21
+ export declare function GetAuthService(app: string, apiURL: string): AuthService;
22
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,106 @@
1
+ import { getClient } from './client/client';
2
+ import { Storage, StorageKey } from './client/storage/localStorage';
3
+ class AuthService {
4
+ app;
5
+ apiURL;
6
+ // теперь обещание возвращает сам новый токен или null
7
+ refreshPromise = null;
8
+ constructor(app, apiURL) {
9
+ this.app = app;
10
+ this.apiURL = apiURL;
11
+ }
12
+ withApp(app) {
13
+ this.app = app;
14
+ return this;
15
+ }
16
+ getClient() {
17
+ return getClient(this.app, this.apiURL);
18
+ }
19
+ /**
20
+ * @description Refresh access token
21
+ * @returns `accessToken` -> refresh ok. `null` -> refresh failed
22
+ **/
23
+ async doRefresh() {
24
+ const { data, error } = await this.getClient().POST('/auth/refresh');
25
+ if (error) {
26
+ console.error('Refresh error:', error);
27
+ Storage.Delete(StorageKey.ACCESS_TOKEN);
28
+ return null;
29
+ }
30
+ if (data?.accessToken) {
31
+ Storage.Set(StorageKey.ACCESS_TOKEN, data.accessToken);
32
+ return data.accessToken;
33
+ }
34
+ Storage.Delete(StorageKey.ACCESS_TOKEN);
35
+ return null;
36
+ }
37
+ // Validate token
38
+ async Validate() {
39
+ const token = Storage.Get(StorageKey.ACCESS_TOKEN);
40
+ if (!token) {
41
+ return null;
42
+ }
43
+ const { response, data, error } = await this.getClient().GET('/auth/me', {
44
+ params: {
45
+ query: {
46
+ app: this.app,
47
+ },
48
+ header: {
49
+ Authorization: `Bearer ${token}`,
50
+ },
51
+ },
52
+ });
53
+ if (error) {
54
+ console.error(error);
55
+ return null;
56
+ }
57
+ if (!data || !data.user_id) {
58
+ console.warn('Failed to get user info: ', data);
59
+ return null;
60
+ }
61
+ return {
62
+ userId: data.user_id,
63
+ roles: data.roles || [],
64
+ };
65
+ }
66
+ WithAuthorizationToken(request, token = null) {
67
+ token ??= Storage.Get(StorageKey.ACCESS_TOKEN);
68
+ if (token) {
69
+ request.headers.set('Authorization', `Bearer ${token}`);
70
+ }
71
+ return request;
72
+ }
73
+ async RefreshAndRetry(request, response) {
74
+ if (!this.refreshPromise) {
75
+ this.refreshPromise = this.doRefresh();
76
+ }
77
+ let token = null;
78
+ try {
79
+ token = await this.refreshPromise;
80
+ }
81
+ catch (e) {
82
+ console.error(e);
83
+ }
84
+ finally {
85
+ this.refreshPromise = null;
86
+ }
87
+ if (!token) {
88
+ return response;
89
+ }
90
+ try {
91
+ const originalRequest = request instanceof Request ? request : new Request(request);
92
+ const retriedResponse = await fetch(this.WithAuthorizationToken(originalRequest.clone(), token));
93
+ return retriedResponse;
94
+ }
95
+ catch (e) {
96
+ return response;
97
+ }
98
+ }
99
+ }
100
+ let auth = null;
101
+ export function GetAuthService(app, apiURL) {
102
+ if (!auth) {
103
+ auth = new AuthService(app, apiURL);
104
+ }
105
+ return auth.withApp(app);
106
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@dehwyyy/auth",
3
+ "version": "1.0.2",
4
+ "private": false,
5
+ "description": "dehwyyy auth utilities",
6
+ "type": "module",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./guard": {
14
+ "import": "./dist/guard/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc",
22
+ "prepack": "npm run build",
23
+ "lint": "prettier --check .",
24
+ "ts": "tsc",
25
+ "format": "prettier --write ."
26
+ },
27
+ "engines": {
28
+ "bun": ">=0.5.0"
29
+ },
30
+ "keywords": [
31
+ "auth",
32
+ "dehwyyy",
33
+ "dehwyyyauth"
34
+ ],
35
+ "author": "",
36
+ "license": "MIT",
37
+ "devDependencies": {
38
+ "prettier": "^3.0.0",
39
+ "typescript": "5.8.2"
40
+ },
41
+ "dependencies": {
42
+ "openapi-fetch": "^0.14.0"
43
+ }
44
+ }