@plyaz/auth 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/.github/pull_request_template.md +71 -0
- package/.github/workflows/deploy.yml +9 -0
- package/.github/workflows/publish.yml +14 -0
- package/.github/workflows/security.yml +20 -0
- package/README.md +89 -0
- package/commits.txt +5 -0
- package/dist/common/index.cjs +48 -0
- package/dist/common/index.cjs.map +1 -0
- package/dist/common/index.mjs +43 -0
- package/dist/common/index.mjs.map +1 -0
- package/dist/index.cjs +20411 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.mjs +5139 -0
- package/dist/index.mjs.map +1 -0
- package/eslint.config.mjs +13 -0
- package/index.html +13 -0
- package/package.json +141 -0
- package/src/adapters/auth-adapter-factory.ts +26 -0
- package/src/adapters/auth-adapter.mapper.ts +53 -0
- package/src/adapters/base-auth.adapter.ts +119 -0
- package/src/adapters/clerk/clerk.adapter.ts +204 -0
- package/src/adapters/custom/custom.adapter.ts +119 -0
- package/src/adapters/index.ts +4 -0
- package/src/adapters/next-auth/authOptions.ts +81 -0
- package/src/adapters/next-auth/next-auth.adapter.ts +211 -0
- package/src/api/client.ts +37 -0
- package/src/audit/audit.logger.ts +52 -0
- package/src/client/components/ProtectedRoute.tsx +37 -0
- package/src/client/hooks/useAuth.ts +128 -0
- package/src/client/hooks/useConnectedAccounts.ts +108 -0
- package/src/client/hooks/usePermissions.ts +36 -0
- package/src/client/hooks/useRBAC.ts +36 -0
- package/src/client/hooks/useSession.ts +18 -0
- package/src/client/providers/AuthProvider.tsx +104 -0
- package/src/client/store/auth.store.ts +306 -0
- package/src/client/utils/storage.ts +70 -0
- package/src/common/constants/oauth-providers.ts +49 -0
- package/src/common/errors/auth.errors.ts +64 -0
- package/src/common/errors/specific-auth-errors.ts +201 -0
- package/src/common/index.ts +19 -0
- package/src/common/regex/index.ts +27 -0
- package/src/common/types/auth.types.ts +641 -0
- package/src/common/types/index.ts +297 -0
- package/src/common/utils/index.ts +84 -0
- package/src/core/blacklist/token.blacklist.ts +60 -0
- package/src/core/index.ts +2 -0
- package/src/core/jwt/jwt.manager.ts +131 -0
- package/src/core/session/session.manager.ts +56 -0
- package/src/db/repositories/connected-account.repository.ts +415 -0
- package/src/db/repositories/role.repository.ts +519 -0
- package/src/db/repositories/session.repository.ts +308 -0
- package/src/db/repositories/user.repository.ts +320 -0
- package/src/flows/index.ts +2 -0
- package/src/flows/sign-in.flow.ts +106 -0
- package/src/flows/sign-up.flow.ts +121 -0
- package/src/index.ts +54 -0
- package/src/libs/clerk.helper.ts +36 -0
- package/src/libs/supabase.helper.ts +255 -0
- package/src/libs/supabaseClient.ts +6 -0
- package/src/providers/base/auth-provider.interface.ts +42 -0
- package/src/providers/base/index.ts +1 -0
- package/src/providers/index.ts +2 -0
- package/src/providers/oauth/facebook.provider.ts +97 -0
- package/src/providers/oauth/github.provider.ts +148 -0
- package/src/providers/oauth/google.provider.ts +126 -0
- package/src/providers/oauth/index.ts +3 -0
- package/src/rbac/dynamic-roles.ts +552 -0
- package/src/rbac/index.ts +4 -0
- package/src/rbac/permission-checker.ts +464 -0
- package/src/rbac/role-hierarchy.ts +545 -0
- package/src/rbac/role.manager.ts +75 -0
- package/src/security/csrf/csrf.protection.ts +37 -0
- package/src/security/index.ts +3 -0
- package/src/security/rate-limiting/auth/auth.controller.ts +12 -0
- package/src/security/rate-limiting/auth/rate-limiting.interface.ts +67 -0
- package/src/security/rate-limiting/auth.module.ts +32 -0
- package/src/server/auth.module.ts +158 -0
- package/src/server/decorators/auth.decorator.ts +43 -0
- package/src/server/decorators/auth.decorators.ts +31 -0
- package/src/server/decorators/current-user.decorator.ts +49 -0
- package/src/server/decorators/permission.decorator.ts +49 -0
- package/src/server/guards/auth.guard.ts +56 -0
- package/src/server/guards/custom-throttler.guard.ts +46 -0
- package/src/server/guards/permissions.guard.ts +115 -0
- package/src/server/guards/roles.guard.ts +31 -0
- package/src/server/middleware/auth.middleware.ts +46 -0
- package/src/server/middleware/index.ts +2 -0
- package/src/server/middleware/middleware.ts +11 -0
- package/src/server/middleware/session.middleware.ts +255 -0
- package/src/server/services/account.service.ts +269 -0
- package/src/server/services/auth.service.ts +79 -0
- package/src/server/services/brute-force.service.ts +98 -0
- package/src/server/services/index.ts +15 -0
- package/src/server/services/rate-limiter.service.ts +60 -0
- package/src/server/services/session.service.ts +287 -0
- package/src/server/services/token.service.ts +262 -0
- package/src/session/cookie-store.ts +255 -0
- package/src/session/enhanced-session-manager.ts +406 -0
- package/src/session/index.ts +14 -0
- package/src/session/memory-store.ts +320 -0
- package/src/session/redis-store.ts +443 -0
- package/src/strategies/oauth.strategy.ts +128 -0
- package/src/strategies/traditional-auth.strategy.ts +116 -0
- package/src/tokens/index.ts +4 -0
- package/src/tokens/refresh-token-manager.ts +448 -0
- package/src/tokens/token-validator.ts +311 -0
- package/tsconfig.build.json +28 -0
- package/tsconfig.json +38 -0
- package/tsup.config.mjs +28 -0
- package/vitest.config.mjs +16 -0
- package/vitest.setup.d.ts +2 -0
- package/vitest.setup.d.ts.map +1 -0
- package/vitest.setup.ts +1 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useAuth } from './useAuth';
|
|
2
|
+
|
|
3
|
+
export interface UseRBACReturn {
|
|
4
|
+
hasRole: (role: string) => boolean;
|
|
5
|
+
hasAnyRole: (roles: string[]) => boolean;
|
|
6
|
+
hasAllRoles: (roles: string[]) => boolean;
|
|
7
|
+
userRoles: string[];
|
|
8
|
+
isAdmin: boolean;
|
|
9
|
+
isModerator: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const useRBAC = (): UseRBACReturn => {
|
|
13
|
+
const { user } = useAuth();
|
|
14
|
+
const userRoles = user?.roles ?? [];
|
|
15
|
+
|
|
16
|
+
const hasRole = (role: string): boolean => {
|
|
17
|
+
return userRoles.includes(role);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const hasAnyRole = (roles: string[]): boolean => {
|
|
21
|
+
return roles.some(role => userRoles.includes(role));
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const hasAllRoles = (roles: string[]): boolean => {
|
|
25
|
+
return roles.every(role => userRoles.includes(role));
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
hasRole,
|
|
30
|
+
hasAnyRole,
|
|
31
|
+
hasAllRoles,
|
|
32
|
+
userRoles,
|
|
33
|
+
isAdmin: hasRole('ADMIN'),
|
|
34
|
+
isModerator: hasRole('MODERATOR')
|
|
35
|
+
};
|
|
36
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Session } from "@plyaz/types";
|
|
2
|
+
import { useAuthStore } from "../store/auth.store";
|
|
3
|
+
|
|
4
|
+
export function useSession() : {
|
|
5
|
+
session: Session | null;
|
|
6
|
+
isValid: boolean;
|
|
7
|
+
expiresAt: Date | null;
|
|
8
|
+
refresh: () => Promise<void>;
|
|
9
|
+
} {
|
|
10
|
+
const store = useAuthStore();
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
session: store.session,
|
|
14
|
+
isValid: !!store.session,
|
|
15
|
+
expiresAt: store.sessionExpiry,
|
|
16
|
+
refresh: store.refreshTokens,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { createContext } from "react";
|
|
3
|
+
import type { UseAuthReturn } from "../hooks/useAuth";
|
|
4
|
+
import { useAuthStore } from "../store/auth.store";
|
|
5
|
+
import type {
|
|
6
|
+
AuthCredentials,
|
|
7
|
+
AUTHPROVIDER,
|
|
8
|
+
ConnectedAccount,
|
|
9
|
+
AuthAdapterUser,
|
|
10
|
+
AuthSession,
|
|
11
|
+
Tokens
|
|
12
|
+
} from "@plyaz/types";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* React context for authentication.
|
|
16
|
+
* Provides user, loading, error states and authentication functions.
|
|
17
|
+
*/
|
|
18
|
+
export const AuthContext = createContext<UseAuthReturn | null>(null);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* AuthProvider component that wraps your app.
|
|
22
|
+
* Uses Zustand store for all authentication state and actions.
|
|
23
|
+
*
|
|
24
|
+
* @param children React children components
|
|
25
|
+
*/
|
|
26
|
+
export function AuthProvider({ children }: { children: ReactNode }): ReactNode {
|
|
27
|
+
const store = useAuthStore();
|
|
28
|
+
|
|
29
|
+
// Wrap actions in a helper to handle loading and errors
|
|
30
|
+
const handleAuthAction = async <T,>(
|
|
31
|
+
action: () => Promise<T>
|
|
32
|
+
): Promise<T> => {
|
|
33
|
+
store.setLoading(true);
|
|
34
|
+
store.setError(null);
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const result = await action();
|
|
38
|
+
return result;
|
|
39
|
+
} catch (err: unknown) {
|
|
40
|
+
const errorMessage =
|
|
41
|
+
err instanceof Error ? err.message : "Authentication failed";
|
|
42
|
+
store.setError(errorMessage);
|
|
43
|
+
throw err;
|
|
44
|
+
} finally {
|
|
45
|
+
store.setLoading(false);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Create the auth context value
|
|
50
|
+
const authValue = {
|
|
51
|
+
user: store.user,
|
|
52
|
+
isAuthenticated: store.isAuthenticated,
|
|
53
|
+
isLoading: store.isLoading,
|
|
54
|
+
error: store.error,
|
|
55
|
+
|
|
56
|
+
// Auth API actions wrapped with handleAuthAction
|
|
57
|
+
signIn: async (provider?: AUTHPROVIDER, credentials?: AuthCredentials): Promise<{
|
|
58
|
+
user: AuthAdapterUser;
|
|
59
|
+
session: AuthSession;
|
|
60
|
+
tokens: Tokens;
|
|
61
|
+
}> => {
|
|
62
|
+
return handleAuthAction(() => store.signIn(provider, credentials));
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
signUp: async (provider: AUTHPROVIDER, credentials:unknown, data?:unknown) => {
|
|
66
|
+
return handleAuthAction(() => store.signUp(provider, data));
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
signOut: async (): Promise<void> => {
|
|
70
|
+
return handleAuthAction(() => store.signOut());
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
linkAccount: async (userId:string,provider:AUTHPROVIDER,data:ConnectedAccount): Promise<void |ConnectedAccount> => {
|
|
74
|
+
return handleAuthAction(async () => {
|
|
75
|
+
// Create a minimal ConnectedAccount object with only required properties
|
|
76
|
+
const connectedAccount: ConnectedAccount = {
|
|
77
|
+
|
|
78
|
+
...data,
|
|
79
|
+
userId,
|
|
80
|
+
provider,
|
|
81
|
+
providerType: "",
|
|
82
|
+
providerAccountId: "",
|
|
83
|
+
isPrimary: false,
|
|
84
|
+
isVerified: false,
|
|
85
|
+
isActive: false,
|
|
86
|
+
linkedAt: new Date(),
|
|
87
|
+
createdAt: new Date(),
|
|
88
|
+
updatedAt: new Date()
|
|
89
|
+
};
|
|
90
|
+
store.addConnectedAccount(connectedAccount);
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
unlinkAccount: async (id: string): Promise<void> => {
|
|
95
|
+
return handleAuthAction(async () => store.removeConnectedAccount(id));
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<AuthContext.Provider value={authValue}>
|
|
101
|
+
{children}
|
|
102
|
+
</AuthContext.Provider>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
|
|
2
|
+
import type { AuthAdapterUser, AuthCredentials, AUTHPROVIDER, AuthSession, AuthTokens,AuthUser,ConnectedAccount, Session, Tokens, } from "@plyaz/types";
|
|
3
|
+
import { create } from "zustand";
|
|
4
|
+
import { persist, subscribeWithSelector } from "zustand/middleware";
|
|
5
|
+
import type { AuthPermissions } from "../hooks/useAuth";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
interface AuthState {
|
|
9
|
+
// User & Authentication
|
|
10
|
+
user: AuthPermissions | null;
|
|
11
|
+
tokens: AuthTokens | null;
|
|
12
|
+
isAuthenticated: boolean;
|
|
13
|
+
isLoading: boolean;
|
|
14
|
+
|
|
15
|
+
// Session Management
|
|
16
|
+
session: Session | null;
|
|
17
|
+
sessionExpiry: Date | null;
|
|
18
|
+
|
|
19
|
+
// Connected Accounts
|
|
20
|
+
connectedAccounts: ConnectedAccount[];
|
|
21
|
+
|
|
22
|
+
// Permissions & Roles
|
|
23
|
+
permissions: string[];
|
|
24
|
+
roles: string[];
|
|
25
|
+
|
|
26
|
+
// UI State
|
|
27
|
+
lastActivity: Date | null;
|
|
28
|
+
rememberMe: boolean;
|
|
29
|
+
|
|
30
|
+
// Error State
|
|
31
|
+
error: string | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
export interface AuthActions {
|
|
37
|
+
// User Management
|
|
38
|
+
setUser: (user: AuthUser | null) => void;
|
|
39
|
+
updateUser: (updates: Partial<AuthUser>) => void;
|
|
40
|
+
|
|
41
|
+
// Token Management
|
|
42
|
+
setTokens: (tokens: AuthTokens | null) => void;
|
|
43
|
+
refreshTokens: () => Promise<void>;
|
|
44
|
+
|
|
45
|
+
// Session Management
|
|
46
|
+
setSession: (session: Session | null) => void;
|
|
47
|
+
updateLastActivity: () => void;
|
|
48
|
+
|
|
49
|
+
// Connected Accounts
|
|
50
|
+
setConnectedAccounts: (accounts: ConnectedAccount[]) => void;
|
|
51
|
+
addConnectedAccount: (account: ConnectedAccount) => void;
|
|
52
|
+
removeConnectedAccount: (accountId: string) => void;
|
|
53
|
+
|
|
54
|
+
// Permissions & Roles
|
|
55
|
+
setPermissions: (permissions: string[]) => void;
|
|
56
|
+
setRoles: (roles: string[]) => void;
|
|
57
|
+
hasPermission: (permission: string) => boolean;
|
|
58
|
+
hasRole: (role: string) => boolean;
|
|
59
|
+
|
|
60
|
+
// Authentication Actions
|
|
61
|
+
signIn: (provider?: AUTHPROVIDER, credentials?: AuthCredentials) => Promise<{
|
|
62
|
+
user: AuthAdapterUser;
|
|
63
|
+
session: AuthSession;
|
|
64
|
+
tokens: Tokens;
|
|
65
|
+
}>;
|
|
66
|
+
signUp: (provider: AUTHPROVIDER, data?: unknown) => Promise<void>;
|
|
67
|
+
signOut: () => Promise<void>;
|
|
68
|
+
|
|
69
|
+
// State Management
|
|
70
|
+
setLoading: (loading: boolean) => void;
|
|
71
|
+
setError: (error: string | null) => void;
|
|
72
|
+
clearError: () => void;
|
|
73
|
+
|
|
74
|
+
// Utility
|
|
75
|
+
reset: () => void;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const initialState: AuthState = {
|
|
79
|
+
user: null,
|
|
80
|
+
tokens: null,
|
|
81
|
+
isAuthenticated: false,
|
|
82
|
+
isLoading: true,
|
|
83
|
+
session: null,
|
|
84
|
+
sessionExpiry: null,
|
|
85
|
+
connectedAccounts: [],
|
|
86
|
+
permissions: [],
|
|
87
|
+
roles: [],
|
|
88
|
+
lastActivity: null,
|
|
89
|
+
rememberMe: false,
|
|
90
|
+
error: null,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export const useAuthStore = create<AuthState & AuthActions>()(
|
|
94
|
+
subscribeWithSelector(
|
|
95
|
+
persist(
|
|
96
|
+
// eslint-disable-next-line max-lines-per-function
|
|
97
|
+
(set, get) => ({
|
|
98
|
+
...initialState,
|
|
99
|
+
|
|
100
|
+
// User Management
|
|
101
|
+
setUser: (user) =>
|
|
102
|
+
set({
|
|
103
|
+
user,
|
|
104
|
+
isAuthenticated: !!user,
|
|
105
|
+
error: null,
|
|
106
|
+
}),
|
|
107
|
+
|
|
108
|
+
updateUser: (updates) => {
|
|
109
|
+
const { user } = get();
|
|
110
|
+
if (user) {
|
|
111
|
+
set({ user: { ...user, ...updates } });
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
// Token Management
|
|
116
|
+
setTokens: (tokens) => set({ tokens }),
|
|
117
|
+
|
|
118
|
+
refreshTokens: async () => {
|
|
119
|
+
const { tokens } = get();
|
|
120
|
+
if (!tokens?.refreshToken) return;
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const response = await globalThis.fetch("/api/auth/refresh", {
|
|
124
|
+
method: "POST",
|
|
125
|
+
headers: { "Content-Type": "application/json" },
|
|
126
|
+
body: JSON.stringify({ refreshToken: tokens.refreshToken }),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (response.ok) {
|
|
130
|
+
const newTokens = (await response.json()) as AuthTokens;
|
|
131
|
+
set({ tokens: newTokens });
|
|
132
|
+
} else {
|
|
133
|
+
// Refresh failed, sign out
|
|
134
|
+
await get().signOut();
|
|
135
|
+
}
|
|
136
|
+
} catch (error: unknown) {
|
|
137
|
+
globalThis.console.log(error)
|
|
138
|
+
await get().signOut();
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
// Session Management
|
|
143
|
+
setSession: (session) =>
|
|
144
|
+
set({
|
|
145
|
+
session,
|
|
146
|
+
sessionExpiry: session?.expiresAt
|
|
147
|
+
? new Date(session.expiresAt)
|
|
148
|
+
: null,
|
|
149
|
+
}),
|
|
150
|
+
|
|
151
|
+
updateLastActivity: () => set({ lastActivity: new Date() }),
|
|
152
|
+
|
|
153
|
+
// Connected Accounts
|
|
154
|
+
setConnectedAccounts: (connectedAccounts) => set({ connectedAccounts }),
|
|
155
|
+
|
|
156
|
+
addConnectedAccount: (account) => {
|
|
157
|
+
const { connectedAccounts } = get();
|
|
158
|
+
set({ connectedAccounts: [...connectedAccounts, account] });
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
removeConnectedAccount: (accountId) => {
|
|
162
|
+
const { connectedAccounts } = get();
|
|
163
|
+
set({
|
|
164
|
+
connectedAccounts: connectedAccounts.filter(
|
|
165
|
+
(acc) => acc.id !== accountId
|
|
166
|
+
),
|
|
167
|
+
});
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
// Permissions & Roles
|
|
171
|
+
setPermissions: (permissions) => set({ permissions }),
|
|
172
|
+
setRoles: (roles) => set({ roles }),
|
|
173
|
+
|
|
174
|
+
hasPermission: (permission) => {
|
|
175
|
+
const { permissions } = get();
|
|
176
|
+
return permissions.includes(permission);
|
|
177
|
+
},
|
|
178
|
+
|
|
179
|
+
hasRole: (role) => {
|
|
180
|
+
const { roles } = get();
|
|
181
|
+
return roles.includes(role);
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
// Authentication Actions
|
|
185
|
+
signUp: async (provider, data?: unknown) => {
|
|
186
|
+
set({ isLoading: true, error: null });
|
|
187
|
+
try {
|
|
188
|
+
const endpoint = provider
|
|
189
|
+
? `/api/auth/signup/${provider}`
|
|
190
|
+
: "/api/auth/signup";
|
|
191
|
+
const response = await globalThis.fetch(endpoint, {
|
|
192
|
+
method: "POST",
|
|
193
|
+
headers: { "Content-Type": "application/json" },
|
|
194
|
+
body: JSON.stringify(data),
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (!response.ok) {
|
|
198
|
+
const errorData = (await response.json()) as { message?: string };
|
|
199
|
+
throw new Error(errorData.message ?? "Sign up failed");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const responseData = (await response.json()) as {
|
|
203
|
+
user: AuthUser;
|
|
204
|
+
tokens: AuthTokens;
|
|
205
|
+
session: Session;
|
|
206
|
+
};
|
|
207
|
+
const { user, tokens, session } = responseData;
|
|
208
|
+
set({
|
|
209
|
+
user,
|
|
210
|
+
tokens,
|
|
211
|
+
session,
|
|
212
|
+
isAuthenticated: true,
|
|
213
|
+
lastActivity: new Date(),
|
|
214
|
+
});
|
|
215
|
+
} catch (error: unknown) {
|
|
216
|
+
const errorMessage =
|
|
217
|
+
error instanceof Error ? error.message : "Sign up failed";
|
|
218
|
+
set({ error: errorMessage });
|
|
219
|
+
} finally {
|
|
220
|
+
set({ isLoading: false });
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
signIn: async (provider, credentials) => {
|
|
224
|
+
set({ isLoading: true, error: null });
|
|
225
|
+
try {
|
|
226
|
+
const endpoint = provider
|
|
227
|
+
? `/api/auth/signin/${provider}`
|
|
228
|
+
: "/api/auth/signin";
|
|
229
|
+
const response = await globalThis.fetch(endpoint, {
|
|
230
|
+
method: "POST",
|
|
231
|
+
headers: { "Content-Type": "application/json" },
|
|
232
|
+
body: JSON.stringify(credentials),
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
if (!response.ok) {
|
|
236
|
+
const errorData = (await response.json()) as { message?: string };
|
|
237
|
+
throw new Error(errorData.message ?? "Sign in failed");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const data = (await response.json()) as {
|
|
241
|
+
user: AuthUser;
|
|
242
|
+
tokens: AuthTokens;
|
|
243
|
+
session: Session;
|
|
244
|
+
};
|
|
245
|
+
const { user, tokens, session } = data;
|
|
246
|
+
|
|
247
|
+
// Update the store state
|
|
248
|
+
set({
|
|
249
|
+
user,
|
|
250
|
+
tokens,
|
|
251
|
+
session,
|
|
252
|
+
isAuthenticated: true,
|
|
253
|
+
lastActivity: new Date(),
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Return the expected object with proper types
|
|
257
|
+
return {
|
|
258
|
+
user: user as AuthAdapterUser, // Cast to match expected type
|
|
259
|
+
session: session as AuthSession, // Cast to match expected type
|
|
260
|
+
tokens: tokens as Tokens, // Cast to match expected type
|
|
261
|
+
};
|
|
262
|
+
} catch (error: unknown) {
|
|
263
|
+
const errorMessage =
|
|
264
|
+
error instanceof Error ? error.message : "Sign in failed";
|
|
265
|
+
set({ error: errorMessage });
|
|
266
|
+
// Re-throw the error to maintain the Promise rejection
|
|
267
|
+
throw error;
|
|
268
|
+
} finally {
|
|
269
|
+
set({ isLoading: false });
|
|
270
|
+
}
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
signOut: async () => {
|
|
274
|
+
set({ isLoading: true });
|
|
275
|
+
try {
|
|
276
|
+
await globalThis.fetch("/api/auth/signout", { method: "POST" });
|
|
277
|
+
set(initialState);
|
|
278
|
+
} catch (error: unknown) {
|
|
279
|
+
const errorMessage =
|
|
280
|
+
error instanceof Error ? error.message : "Sign out failed";
|
|
281
|
+
set({ error: errorMessage });
|
|
282
|
+
} finally {
|
|
283
|
+
set({ isLoading: false });
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
// State Management
|
|
288
|
+
setLoading: (isLoading) => set({ isLoading }),
|
|
289
|
+
setError: (error) => set({ error }),
|
|
290
|
+
clearError: () => set({ error: null }),
|
|
291
|
+
|
|
292
|
+
// Utility
|
|
293
|
+
reset: () => set(initialState),
|
|
294
|
+
}),
|
|
295
|
+
{
|
|
296
|
+
name: "auth-storage",
|
|
297
|
+
partialize: (state) => ({
|
|
298
|
+
user: state.user,
|
|
299
|
+
tokens: state.tokens,
|
|
300
|
+
rememberMe: state.rememberMe,
|
|
301
|
+
connectedAccounts: state.connectedAccounts,
|
|
302
|
+
}),
|
|
303
|
+
}
|
|
304
|
+
)
|
|
305
|
+
)
|
|
306
|
+
);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Storage utility for client-side operations
|
|
4
|
+
* @module @plyaz/auth/client/utils/storage
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
class StorageManager {
|
|
8
|
+
private get storage() {
|
|
9
|
+
return typeof globalThis !== 'undefined' ? (globalThis ).localStorage : null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getItem(key: string): string | null {
|
|
13
|
+
if (!this.storage) return null;
|
|
14
|
+
try {
|
|
15
|
+
return this.storage.getItem(key);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
if (error instanceof DOMException && error.name === 'QuotaExceededError') {
|
|
18
|
+
globalThis.console.warn('localStorage quota exceeded:', error);
|
|
19
|
+
} else if (error instanceof DOMException && error.name === 'SecurityError') {
|
|
20
|
+
globalThis.console.warn('localStorage access denied (private browsing):', error);
|
|
21
|
+
} else {
|
|
22
|
+
globalThis.console.warn('localStorage.getItem failed:', error);
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setItem(key: string, value: string): void {
|
|
29
|
+
if (!this.storage) return;
|
|
30
|
+
try {
|
|
31
|
+
this.storage.setItem(key, value);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (error instanceof DOMException && error.name === 'QuotaExceededError') {
|
|
34
|
+
globalThis.console.warn('localStorage quota exceeded, cannot save:', error);
|
|
35
|
+
} else if (error instanceof DOMException && error.name === 'SecurityError') {
|
|
36
|
+
globalThis.console.warn('localStorage access denied (private browsing):', error);
|
|
37
|
+
} else {
|
|
38
|
+
globalThis.console.warn('localStorage.setItem failed:', error);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
removeItem(key: string): void {
|
|
44
|
+
if (!this.storage) return;
|
|
45
|
+
try {
|
|
46
|
+
this.storage.removeItem(key);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (error instanceof DOMException && error.name === 'SecurityError') {
|
|
49
|
+
globalThis.console.warn('localStorage access denied (private browsing):', error);
|
|
50
|
+
} else {
|
|
51
|
+
globalThis.console.warn('localStorage.removeItem failed:', error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
clear(): void {
|
|
57
|
+
if (!this.storage) return;
|
|
58
|
+
try {
|
|
59
|
+
this.storage.clear();
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (error instanceof DOMException && error.name === 'SecurityError') {
|
|
62
|
+
globalThis. console.warn('localStorage access denied (private browsing):', error);
|
|
63
|
+
} else {
|
|
64
|
+
globalThis.console.warn('localStorage.clear failed:', error);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const storage = new StorageManager();
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// /**
|
|
2
|
+
// * @fileoverview OAuth provider constants for @plyaz/auth
|
|
3
|
+
// * @module @plyaz/auth/constants/oauth-providers
|
|
4
|
+
// *
|
|
5
|
+
// * @description
|
|
6
|
+
// * Defines supported OAuth providers and their configurations.
|
|
7
|
+
// * Used by adapters, strategies, and frontend components to handle
|
|
8
|
+
// * OAuth authentication flows. Provides standardized provider names
|
|
9
|
+
// * and metadata for consistent provider handling.
|
|
10
|
+
// *
|
|
11
|
+
// * @example
|
|
12
|
+
// * ```typescript
|
|
13
|
+
// * import { OAUTH_PROVIDERS, OAuthProviderConfig } from '@plyaz/auth';
|
|
14
|
+
// *
|
|
15
|
+
// * const googleConfig = OAUTH_PROVIDER_CONFIGS[OAUTH_PROVIDERS.GOOGLE];
|
|
16
|
+
// * const authUrl = `${googleConfig.authUrl}?client_id=${clientId}`;
|
|
17
|
+
// * ```
|
|
18
|
+
// */
|
|
19
|
+
|
|
20
|
+
import { OAUTH_PROVIDER_CONFIGS } from "@plyaz/config";
|
|
21
|
+
import type { OAuthProvider, OAuthProviderConfig } from "@plyaz/types";
|
|
22
|
+
import { OAUTH_PROVIDERS } from "@plyaz/types";
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get OAuth provider configuration by provider name
|
|
27
|
+
* @param provider - OAuth provider name
|
|
28
|
+
* @returns Provider configuration or null if not found
|
|
29
|
+
*/
|
|
30
|
+
export function getOAuthProviderConfig(provider: string): OAuthProviderConfig | null {
|
|
31
|
+
return OAUTH_PROVIDER_CONFIGS[provider as OAuthProvider] || null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if provider is supported
|
|
36
|
+
* @param provider - Provider name to check
|
|
37
|
+
* @returns True if provider is supported
|
|
38
|
+
*/
|
|
39
|
+
export function isOAuthProviderSupported(provider: string): provider is OAuthProvider {
|
|
40
|
+
return Object.values(OAUTH_PROVIDERS).includes(provider as OAuthProvider);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get all supported OAuth provider names
|
|
45
|
+
* @returns Array of supported provider names
|
|
46
|
+
*/
|
|
47
|
+
export function getSupportedOAuthProviders(): OAuthProvider[] {
|
|
48
|
+
return Object.values(OAUTH_PROVIDERS);
|
|
49
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// /**
|
|
2
|
+
// * @fileoverview Authentication error classes for @plyaz/auth
|
|
3
|
+
// * @module @plyaz/auth/errors
|
|
4
|
+
// *
|
|
5
|
+
// * @description
|
|
6
|
+
// * Defines custom error classes for authentication and authorization failures.
|
|
7
|
+
// * These errors provide structured error information for proper error handling
|
|
8
|
+
// * throughout the authentication system. Includes both specific error classes
|
|
9
|
+
// * and legacy compatibility classes.
|
|
10
|
+
// *
|
|
11
|
+
// * @example
|
|
12
|
+
// * ```typescript
|
|
13
|
+
// * import { InvalidCredentialsError, TokenExpiredError } from '@plyaz/auth';
|
|
14
|
+
// *
|
|
15
|
+
// * throw new InvalidCredentialsError('Invalid email or password');
|
|
16
|
+
// * throw new TokenExpiredError('Access token has expired');
|
|
17
|
+
// * ```
|
|
18
|
+
// */
|
|
19
|
+
|
|
20
|
+
// // Re-export all specific error classes
|
|
21
|
+
|
|
22
|
+
// // Legacy error classes for backward compatibility
|
|
23
|
+
// export class AuthError extends Error {
|
|
24
|
+
// constructor(message: string, public code: string) {
|
|
25
|
+
// super(message);
|
|
26
|
+
// this.name = 'AuthError';
|
|
27
|
+
// }
|
|
28
|
+
// }
|
|
29
|
+
|
|
30
|
+
// export class AuthenticationError extends AuthError {
|
|
31
|
+
// constructor(message = 'Authentication failed') {
|
|
32
|
+
// super(message, 'AUTH_FAILED');
|
|
33
|
+
// }
|
|
34
|
+
// }
|
|
35
|
+
|
|
36
|
+
// export class AuthorizationError extends AuthError {
|
|
37
|
+
// constructor(message = 'Access denied') {
|
|
38
|
+
// super(message, 'ACCESS_DENIED');
|
|
39
|
+
// }
|
|
40
|
+
// }
|
|
41
|
+
|
|
42
|
+
// export class TokenExpiredError extends AuthError {
|
|
43
|
+
// constructor(message = 'Token has expired') {
|
|
44
|
+
// super(message, 'TOKEN_EXPIRED');
|
|
45
|
+
// }
|
|
46
|
+
// }
|
|
47
|
+
|
|
48
|
+
// export class InvalidTokenError extends AuthError {
|
|
49
|
+
// constructor(message = 'Invalid token') {
|
|
50
|
+
// super(message, 'INVALID_TOKEN');
|
|
51
|
+
// }
|
|
52
|
+
// }
|
|
53
|
+
|
|
54
|
+
// export class SessionNotFoundError extends AuthError {
|
|
55
|
+
// constructor(message = 'Session not found') {
|
|
56
|
+
// super(message, 'SESSION_NOT_FOUND');
|
|
57
|
+
// }
|
|
58
|
+
// }
|
|
59
|
+
|
|
60
|
+
// export class UserNotFoundError extends AuthError {
|
|
61
|
+
// constructor(message = 'User not found') {
|
|
62
|
+
// super(message, 'USER_NOT_FOUND');
|
|
63
|
+
// }
|
|
64
|
+
// }
|