@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,13 @@
|
|
|
1
|
+
import { presets } from '@plyaz/devtools/eslint/base.shared.mjs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
|
|
8
|
+
export default presets.types({
|
|
9
|
+
tsconfigDir: __dirname,
|
|
10
|
+
globals: {
|
|
11
|
+
NodeJS: true,
|
|
12
|
+
},
|
|
13
|
+
});
|
package/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Vite + TS</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/index.ts"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@plyaz/auth",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Authentication package for Plyaz ecosystem",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=23.0.0",
|
|
9
|
+
"pnpm": ">=8.0.0"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"auth",
|
|
13
|
+
"authentication",
|
|
14
|
+
"authorization",
|
|
15
|
+
"nestjs",
|
|
16
|
+
"jwt",
|
|
17
|
+
"rbac",
|
|
18
|
+
"clerk",
|
|
19
|
+
"mfa"
|
|
20
|
+
],
|
|
21
|
+
"packageManager": "pnpm@10.11.0",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@clerk/backend": "^2.29.0",
|
|
24
|
+
"@clerk/clerk-sdk-node": "^4.13.14",
|
|
25
|
+
"@clerk/nextjs": "^6.36.3",
|
|
26
|
+
"@nest-lab/throttler-storage-redis": "^1.1.0",
|
|
27
|
+
"@nestjs/common": "^10.2.8",
|
|
28
|
+
"@nestjs/config": "^4.0.2",
|
|
29
|
+
"@nestjs/core": "^10.2.8",
|
|
30
|
+
"@nestjs/jwt": "^11.0.2",
|
|
31
|
+
"@nestjs/throttler": "^6.5.0",
|
|
32
|
+
"@plyaz/config": "^1.10.2",
|
|
33
|
+
"@plyaz/errors": "^1.5.6",
|
|
34
|
+
"@supabase/supabase-js": "^2.38.4",
|
|
35
|
+
"bcryptjs": "^3.0.3",
|
|
36
|
+
"express": "^5.2.1",
|
|
37
|
+
"ioredis": "^5.8.2",
|
|
38
|
+
"jsonwebtoken": "^9.0.2",
|
|
39
|
+
"next": "^16.1.1",
|
|
40
|
+
"next-auth": "^4.24.13",
|
|
41
|
+
"react": "18.3.1",
|
|
42
|
+
"react-dom": "^18.3.1",
|
|
43
|
+
"zustand": "^4.4.7"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@auth/core": "^0.34.3",
|
|
47
|
+
"@changesets/cli": "^2.29.8",
|
|
48
|
+
"@darraghor/eslint-plugin-nestjs-typed": "^7.1.12",
|
|
49
|
+
"@eslint/eslintrc": "^3.3.3",
|
|
50
|
+
"@eslint/js": "^9.39.2",
|
|
51
|
+
"@eslint/markdown": "^7.5.1",
|
|
52
|
+
"@next/eslint-plugin-next": "^16.1.1",
|
|
53
|
+
"@plyaz/devtools": "^1.10.0",
|
|
54
|
+
"@plyaz/testing": "^1.5.5",
|
|
55
|
+
"@plyaz/types": "^1.45.2",
|
|
56
|
+
"@tanstack/react-query": "^5.90.16",
|
|
57
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
58
|
+
"@testing-library/react": "^16.3.1",
|
|
59
|
+
"@testing-library/user-event": "^14.6.1",
|
|
60
|
+
"@types/express": "^5.0.6",
|
|
61
|
+
"@types/jsonwebtoken": "^9.0.5",
|
|
62
|
+
"@types/node": "^20.8.9",
|
|
63
|
+
"@types/react": "18",
|
|
64
|
+
"@types/react-dom": "18",
|
|
65
|
+
"@typescript-eslint/eslint-plugin": "^8.52.0",
|
|
66
|
+
"@typescript-eslint/parser": "^8.52.0",
|
|
67
|
+
"@vitest/coverage-istanbul": "^4.0.16",
|
|
68
|
+
"@vitest/coverage-v8": "^4.0.16",
|
|
69
|
+
"dotenv": "^16.3.1",
|
|
70
|
+
"eslint": "^9.39.2",
|
|
71
|
+
"eslint-config-next": "^16.1.1",
|
|
72
|
+
"eslint-config-prettier": "^10.1.8",
|
|
73
|
+
"eslint-import-resolver-typescript": "^4.4.4",
|
|
74
|
+
"eslint-mdx": "^3.6.2",
|
|
75
|
+
"eslint-plugin-better-tailwindcss": "^3.8.0",
|
|
76
|
+
"eslint-plugin-functional": "^9.0.2",
|
|
77
|
+
"eslint-plugin-import": "^2.32.0",
|
|
78
|
+
"eslint-plugin-jest": "^29.12.1",
|
|
79
|
+
"eslint-plugin-jsdoc": "^62.0.0",
|
|
80
|
+
"eslint-plugin-jsonc": "^2.21.0",
|
|
81
|
+
"eslint-plugin-jsx-a11y": "^6.10.2",
|
|
82
|
+
"eslint-plugin-markdownlint": "^0.9.0",
|
|
83
|
+
"eslint-plugin-mdx": "^3.6.2",
|
|
84
|
+
"eslint-plugin-n": "^17.23.1",
|
|
85
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
86
|
+
"eslint-plugin-promise": "^7.2.1",
|
|
87
|
+
"eslint-plugin-react": "^7.37.5",
|
|
88
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
89
|
+
"eslint-plugin-react-refresh": "^0.4.26",
|
|
90
|
+
"eslint-plugin-regexp": "^2.10.0",
|
|
91
|
+
"eslint-plugin-security": "^3.0.1",
|
|
92
|
+
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
93
|
+
"eslint-plugin-sonarjs": "^3.0.5",
|
|
94
|
+
"eslint-plugin-storybook": "^10.1.11",
|
|
95
|
+
"eslint-plugin-testing-library": "^7.15.4",
|
|
96
|
+
"eslint-plugin-unicorn": "^62.0.0",
|
|
97
|
+
"eslint-plugin-unused-imports": "^4.3.0",
|
|
98
|
+
"jiti": "^2.6.1",
|
|
99
|
+
"jsdom": "^27.4.0",
|
|
100
|
+
"markdownlint": "^0.40.0",
|
|
101
|
+
"nodemailer": "^7.0.12",
|
|
102
|
+
"prettier": "^3.7.4",
|
|
103
|
+
"standard-version": "^9.5.0",
|
|
104
|
+
"tailwindcss": "^4.1.18",
|
|
105
|
+
"tsup": "^8.5.1",
|
|
106
|
+
"tsx": "^4.6.0",
|
|
107
|
+
"typescript": "5.4.5",
|
|
108
|
+
"vite": "^7.3.1",
|
|
109
|
+
"vite-plugin-dts": "^4.5.4",
|
|
110
|
+
"vitest": "^4.0.16"
|
|
111
|
+
},
|
|
112
|
+
"peerDependencies": {
|
|
113
|
+
"@nestjs/common": ">=8.0.0",
|
|
114
|
+
"react": ">=16.8.0"
|
|
115
|
+
},
|
|
116
|
+
"scripts": {
|
|
117
|
+
"build": "pnpm clean && pnpm build:js && pnpm build:types",
|
|
118
|
+
"build:js": "tsup",
|
|
119
|
+
"build:types": "tsc -p tsconfig.build.json",
|
|
120
|
+
"build:watch": "tsup --watch",
|
|
121
|
+
"dev": "tsup --watch",
|
|
122
|
+
"lint": "eslint ./src",
|
|
123
|
+
"lint:fix": "eslint ./src --fix",
|
|
124
|
+
"format": "prettier --write './**/*.{ts,tsx,js,jsx}'",
|
|
125
|
+
"format:check": "prettier --check './**/*.{ts,tsx,js,jsx}'",
|
|
126
|
+
"type:check": "tsc --noEmit",
|
|
127
|
+
"test": "vitest run --no-watch",
|
|
128
|
+
"test:ci": "vitest run --no-watch",
|
|
129
|
+
"test:watch": "vitest --watch",
|
|
130
|
+
"test:coverage": "vitest run --coverage",
|
|
131
|
+
"test:ui": "vitest --ui",
|
|
132
|
+
"clean": "rm -rf dist",
|
|
133
|
+
"audit": "pnpm audit",
|
|
134
|
+
"audit:moderate": "pnpm audit --audit-level moderate",
|
|
135
|
+
"audit:high": "pnpm audit --audit-level high",
|
|
136
|
+
"audit:critical": "pnpm audit --audit-level critical",
|
|
137
|
+
"audit:fix": "pnpm audit --fix",
|
|
138
|
+
"audit:enhanced": "npx audit-ci --moderate",
|
|
139
|
+
"security:check": "pnpm audit:moderate && npx audit-ci --moderate"
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AuthProviderAdapter } from "@plyaz/types";
|
|
2
|
+
import type { AuthProviderType} from "./auth-adapter.mapper";
|
|
3
|
+
import { resolveAuthAdapter } from "./auth-adapter.mapper";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Factory function to create and return the active authentication adapter.
|
|
7
|
+
*
|
|
8
|
+
* The adapter is resolved based on the `AUTH_PROVIDER` environment variable.
|
|
9
|
+
* If no provider is specified, it defaults to `"custom"`.
|
|
10
|
+
*
|
|
11
|
+
* This allows seamless switching between authentication providers
|
|
12
|
+
* (e.g., Clerk, NextAuth, Custom) without changing application code.
|
|
13
|
+
*
|
|
14
|
+
* @returns A resolved AuthProviderAdapter implementation
|
|
15
|
+
*/
|
|
16
|
+
export function createAuthAdapter():AuthProviderAdapter {
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the authentication provider from environment variables.
|
|
19
|
+
*/
|
|
20
|
+
const provider = (globalThis.process.env.AUTH_PROVIDER as AuthProviderType) ?? "custom";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Return the adapter mapped to the selected provider.
|
|
24
|
+
*/
|
|
25
|
+
return resolveAuthAdapter(provider);
|
|
26
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { AuthProviderAdapter } from "@plyaz/types";
|
|
2
|
+
import { ClerkAdapter } from "./clerk/clerk.adapter";
|
|
3
|
+
import { CustomAdapter } from "./custom/custom.adapter";
|
|
4
|
+
import { NextAuthAdapter } from "./next-auth/next-auth.adapter";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Supported authentication provider types.
|
|
8
|
+
*/
|
|
9
|
+
export type AuthProviderType = "clerk" | "next-auth" | "custom";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Constructor type for an AuthProviderAdapter implementation.
|
|
13
|
+
*
|
|
14
|
+
* Every adapter MUST:
|
|
15
|
+
* - be constructable with `new`
|
|
16
|
+
* - return an object implementing AuthProviderAdapter
|
|
17
|
+
*/
|
|
18
|
+
type AdapterConstructor = new () => AuthProviderAdapter;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Mapping from AuthProviderType → Adapter class.
|
|
22
|
+
*
|
|
23
|
+
* `satisfies` ensures:
|
|
24
|
+
* - No provider keys are missing.
|
|
25
|
+
* - All values are valid constructor functions.
|
|
26
|
+
* - Full type safety at compile time without changing the type of the object.
|
|
27
|
+
*/
|
|
28
|
+
const AUTH_ADAPTER_MAP = {
|
|
29
|
+
clerk: ClerkAdapter,
|
|
30
|
+
"next-auth": NextAuthAdapter,
|
|
31
|
+
custom: CustomAdapter,
|
|
32
|
+
} satisfies Record<AuthProviderType, AdapterConstructor>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Resolves and instantiates the AuthProviderAdapter for a given provider type.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* const adapter = resolveAuthAdapter("clerk");
|
|
39
|
+
* @throws {Error} If the provider type is unknown.
|
|
40
|
+
*/
|
|
41
|
+
export function resolveAuthAdapter(
|
|
42
|
+
provider: AuthProviderType
|
|
43
|
+
): AuthProviderAdapter {
|
|
44
|
+
const Adapter = AUTH_ADAPTER_MAP[provider];
|
|
45
|
+
|
|
46
|
+
// This check is technically redundant due to TypeScript's type checking,
|
|
47
|
+
// but it provides a clear runtime error if the code is somehow bypassed.
|
|
48
|
+
if (!Adapter) {
|
|
49
|
+
throw new Error(`Unknown auth provider: ${provider}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return new Adapter();
|
|
53
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { AuthAdapterUser, AUTHPROVIDER, AuthProviderAdapter, AuthSession, ConnectedAccount, Tokens } from "@plyaz/types";
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* BaseAuthProviderAdapter
|
|
6
|
+
*
|
|
7
|
+
* Abstract base class for all authentication provider adapters.
|
|
8
|
+
* Provides the standard contract for implementing any auth provider
|
|
9
|
+
* (e.g., Clerk, NextAuth, Custom).
|
|
10
|
+
*
|
|
11
|
+
* All concrete adapters must implement these methods to ensure
|
|
12
|
+
* consistency across the system.
|
|
13
|
+
*/
|
|
14
|
+
export abstract class BaseAuthProviderAdapter implements AuthProviderAdapter {
|
|
15
|
+
/**
|
|
16
|
+
* Sign in an existing user.
|
|
17
|
+
*
|
|
18
|
+
* @param provider - Authentication provider identifier
|
|
19
|
+
* @param credentials - Provider-specific credentials
|
|
20
|
+
* @returns Auth result containing user, session, and tokens
|
|
21
|
+
*/
|
|
22
|
+
abstract signIn(
|
|
23
|
+
provider: AUTHPROVIDER,
|
|
24
|
+
credentials?: unknown
|
|
25
|
+
): Promise<{ user: AuthAdapterUser; session: AuthSession; tokens: Tokens }>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Sign up a new user.
|
|
29
|
+
*
|
|
30
|
+
* @param provider - Authentication provider identifier
|
|
31
|
+
* @param credentials - Provider-specific credentials
|
|
32
|
+
* @param data - Optional additional user data
|
|
33
|
+
* @returns Auth result or void if handled externally
|
|
34
|
+
*/
|
|
35
|
+
abstract signUp(
|
|
36
|
+
provider: AUTHPROVIDER,
|
|
37
|
+
credentials: unknown,
|
|
38
|
+
data?: unknown
|
|
39
|
+
): Promise<{ user: AuthAdapterUser; session: AuthSession; tokens: Tokens }> | Promise<void>;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Sign out a user session.
|
|
43
|
+
*
|
|
44
|
+
* @param sessionId - Session identifier
|
|
45
|
+
*/
|
|
46
|
+
abstract signOut(sessionId: string): Promise<void>;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Retrieve a session by its identifier.
|
|
50
|
+
*
|
|
51
|
+
* @param sessionId - Session identifier
|
|
52
|
+
* @returns Session or null if not found or expired
|
|
53
|
+
*/
|
|
54
|
+
abstract getSession(sessionId: string): Promise<AuthSession | null>;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Validate a session.
|
|
58
|
+
*
|
|
59
|
+
* @param sessionId - Session identifier
|
|
60
|
+
* @returns True if session is valid, otherwise false
|
|
61
|
+
*/
|
|
62
|
+
abstract validateSession(sessionId: string): Promise<boolean>;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Refresh an existing session using a refresh token.
|
|
66
|
+
*
|
|
67
|
+
* @param refreshToken - Refresh token
|
|
68
|
+
* @returns New session and tokens, or void if not supported
|
|
69
|
+
*/
|
|
70
|
+
abstract refreshSession(
|
|
71
|
+
refreshToken: string
|
|
72
|
+
): Promise<{ session: AuthSession; tokens: Tokens } | void>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Generate an OAuth authorization URL.
|
|
76
|
+
*
|
|
77
|
+
* @param provider - OAuth provider
|
|
78
|
+
* @param redirectUri - Redirect URI after authentication
|
|
79
|
+
* @returns Authorization URL or void if handled externally
|
|
80
|
+
*/
|
|
81
|
+
abstract getOAuthUrl(
|
|
82
|
+
provider: AUTHPROVIDER,
|
|
83
|
+
redirectUri: string
|
|
84
|
+
): Promise<string | void>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Handle OAuth callback after provider authentication.
|
|
88
|
+
*
|
|
89
|
+
* @param provider - OAuth provider
|
|
90
|
+
* @param code - Authorization code returned by provider
|
|
91
|
+
* @returns Provider account data or void if handled externally
|
|
92
|
+
*/
|
|
93
|
+
abstract handleOAuthCallback(
|
|
94
|
+
provider: AUTHPROVIDER,
|
|
95
|
+
code: string
|
|
96
|
+
): Promise<{ providerAccountId: string; profile: unknown } | void>;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Link an external provider account to an existing user.
|
|
100
|
+
*
|
|
101
|
+
* @param userId - Local user identifier
|
|
102
|
+
* @param provider - Authentication provider
|
|
103
|
+
* @param data - Connected account metadata
|
|
104
|
+
* @returns Linked connected account or void if handled externally
|
|
105
|
+
*/
|
|
106
|
+
abstract linkAccount(
|
|
107
|
+
userId: string,
|
|
108
|
+
provider: AUTHPROVIDER,
|
|
109
|
+
data: ConnectedAccount
|
|
110
|
+
): Promise<ConnectedAccount | void>;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Unlink an external provider account from a user.
|
|
114
|
+
*
|
|
115
|
+
* @param userId - Local user identifier
|
|
116
|
+
* @param accountId - Connected account identifier
|
|
117
|
+
*/
|
|
118
|
+
abstract unlinkAccount(userId: string, accountId: string): Promise<void>;
|
|
119
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
2
|
+
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
3
|
+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
|
4
|
+
|
|
5
|
+
import { clerkClient } from "@clerk/nextjs/server";
|
|
6
|
+
import type { AuthCredentials, AUTHPROVIDER, ConnectedAccount, DeviceInfo } from "@plyaz/types";
|
|
7
|
+
import type { AuthUser } from "@supabase/supabase-js";
|
|
8
|
+
import { DatabasePackageError } from "@plyaz/errors";
|
|
9
|
+
import { BaseAuthProviderAdapter } from "../base-auth.adapter";
|
|
10
|
+
import { createUser } from "../../libs/supabase.helper";
|
|
11
|
+
import { supabase } from "../../libs/supabaseClient";
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* ClerkAdapter
|
|
16
|
+
*
|
|
17
|
+
* Auth provider adapter implementation for Clerk.
|
|
18
|
+
* This adapter delegates authentication and session management to Clerk,
|
|
19
|
+
* while ensuring local user and account synchronization with Supabase.
|
|
20
|
+
*
|
|
21
|
+
* Responsibilities:
|
|
22
|
+
* - Authenticate users via Clerk
|
|
23
|
+
* - Sync Clerk users with local Supabase tables
|
|
24
|
+
* - Maintain compatibility with the unified AuthProviderAdapter interface
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export class ClerkAdapter extends BaseAuthProviderAdapter {
|
|
28
|
+
/**
|
|
29
|
+
* Sign in a user using Clerk.
|
|
30
|
+
*
|
|
31
|
+
* Fetches the Clerk user, syncs it with Supabase, and returns
|
|
32
|
+
* a standardized auth response.
|
|
33
|
+
*
|
|
34
|
+
* @param provider - Auth provider name (unused, Clerk-specific)
|
|
35
|
+
* @param credentials - Clerk credentials containing user ID
|
|
36
|
+
* @param deviceInfo - Optional device metadata
|
|
37
|
+
* @returns Auth result including user, session, and tokens
|
|
38
|
+
*/
|
|
39
|
+
async signIn(
|
|
40
|
+
provider: string,
|
|
41
|
+
credentials: Credential,
|
|
42
|
+
): Promise<{
|
|
43
|
+
user: { id: string; email: string };
|
|
44
|
+
session: { id: string; userId: string; expiresAt: Date };
|
|
45
|
+
tokens: { accessToken: string };
|
|
46
|
+
}> {
|
|
47
|
+
const client = await clerkClient();
|
|
48
|
+
const user = await client.users.getUser(credentials.id);
|
|
49
|
+
await migrateClerkConnection(user.id);
|
|
50
|
+
return {
|
|
51
|
+
user: { id: user.id, email: user.emailAddresses[0].emailAddress },
|
|
52
|
+
session: { id: "clerk-session", userId: user.id, expiresAt: new Date() },
|
|
53
|
+
tokens: { accessToken: "managed-by-clerk" },
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Sign up a new user.
|
|
59
|
+
*
|
|
60
|
+
* User creation is delegated to Clerk, while this method
|
|
61
|
+
* ensures a corresponding Supabase user exists.
|
|
62
|
+
*
|
|
63
|
+
* @param provider - Auth provider identifier
|
|
64
|
+
* @param credentials - Signup credentials
|
|
65
|
+
* @param data - Optional additional user data
|
|
66
|
+
* @param deviceInfo - Optional device metadata
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
async signUp(
|
|
70
|
+
provider: string,
|
|
71
|
+
credentials: AuthCredentials,
|
|
72
|
+
data?: Partial<AuthUser>
|
|
73
|
+
) {
|
|
74
|
+
await createUser(
|
|
75
|
+
credentials.email,
|
|
76
|
+
provider,
|
|
77
|
+
data,
|
|
78
|
+
credentials.password
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Sign out a user.
|
|
84
|
+
*
|
|
85
|
+
* Session handling is fully managed by Clerk.
|
|
86
|
+
*
|
|
87
|
+
* @param sessionId - Session identifier
|
|
88
|
+
*/
|
|
89
|
+
async signOut(sessionId: string) {}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Retrieve a session by ID.
|
|
93
|
+
*
|
|
94
|
+
* Clerk manages sessions externally, so this always returns null.
|
|
95
|
+
*
|
|
96
|
+
* @param sessionId - Session identifier
|
|
97
|
+
* @returns null
|
|
98
|
+
*/
|
|
99
|
+
async getSession(sessionId: string) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Validate a session.
|
|
105
|
+
*
|
|
106
|
+
* Clerk handles session validation internally.
|
|
107
|
+
*
|
|
108
|
+
* @param sessionId - Session identifier
|
|
109
|
+
* @returns true
|
|
110
|
+
*/
|
|
111
|
+
|
|
112
|
+
async validateSession(sessionId: string) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Refresh a session.
|
|
117
|
+
*
|
|
118
|
+
* Not supported because Clerk manages token refresh.
|
|
119
|
+
*
|
|
120
|
+
* @param sessionId - Session identifier
|
|
121
|
+
* @throws DatabasePackageError
|
|
122
|
+
*/
|
|
123
|
+
|
|
124
|
+
async refreshSession(sessionId: string) {
|
|
125
|
+
throw new DatabasePackageError("Not supported");
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Generate an OAuth authorization URL.
|
|
129
|
+
*
|
|
130
|
+
* OAuth flow is handled by Clerk UI.
|
|
131
|
+
*
|
|
132
|
+
* @param provider - OAuth provider
|
|
133
|
+
* @param redirectUri - Redirect URI
|
|
134
|
+
* @throws DatabasePackageError
|
|
135
|
+
*/
|
|
136
|
+
|
|
137
|
+
async getOAuthUrl(provider: string, redirectUri: string) {
|
|
138
|
+
throw new DatabasePackageError("Handled by Clerk");
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Handle OAuth callback.
|
|
142
|
+
*
|
|
143
|
+
* OAuth callbacks are handled by Clerk.
|
|
144
|
+
*
|
|
145
|
+
* @param provider - OAuth provider
|
|
146
|
+
* @param code - Authorization code
|
|
147
|
+
* @throws DatabasePackageError
|
|
148
|
+
*/
|
|
149
|
+
|
|
150
|
+
async handleOAuthCallback(provider: string, code: string) {
|
|
151
|
+
throw new DatabasePackageError("Handled by Clerk");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async linkAccount(
|
|
155
|
+
userId: string,
|
|
156
|
+
provider: AUTHPROVIDER,
|
|
157
|
+
data: ConnectedAccount
|
|
158
|
+
) {
|
|
159
|
+
throw new DatabasePackageError("Handled by Clerk");
|
|
160
|
+
}
|
|
161
|
+
async unlinkAccount(userId: string, accountId: string) {
|
|
162
|
+
throw new DatabasePackageError("Handled by Clerk");
|
|
163
|
+
}
|
|
164
|
+
async getLinkedAccounts(userId: string) {
|
|
165
|
+
throw new DatabasePackageError("Handled by Clerk");
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Sync Clerk user → Supabase users + connected_accounts
|
|
171
|
+
* Safe to call on every sign-in
|
|
172
|
+
*/
|
|
173
|
+
export async function migrateClerkConnection(clerkUserId: string) {
|
|
174
|
+
const { data: existingUser, error: userFetchError } = await supabase
|
|
175
|
+
.from("users")
|
|
176
|
+
.select("id")
|
|
177
|
+
.eq("clerk_user_id", clerkUserId)
|
|
178
|
+
.single();
|
|
179
|
+
|
|
180
|
+
if (userFetchError && userFetchError.code !== "PGRST116") {
|
|
181
|
+
throw userFetchError;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
if (!existingUser) {
|
|
186
|
+
throw new DatabasePackageError("User Not Found");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const { error: accountError } = await supabase
|
|
190
|
+
.from("connected_accounts")
|
|
191
|
+
.upsert({
|
|
192
|
+
provider: "clerk",
|
|
193
|
+
provider_account_id: clerkUserId,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
if (accountError) {
|
|
197
|
+
throw accountError;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
provider: "clerk",
|
|
202
|
+
providerAccountId: clerkUserId,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
|
2
|
+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
|
3
|
+
// custom.adapter.ts
|
|
4
|
+
import type { AuthDeviceInfo, AUTHPROVIDER, AuthUser, ConnectedAccount, AuthCredentials } from "@plyaz/types";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
createUser,
|
|
8
|
+
findUserByEmailProvider,
|
|
9
|
+
updateLastLogin,
|
|
10
|
+
createUserSession,
|
|
11
|
+
getSession,
|
|
12
|
+
validateSessionDB,
|
|
13
|
+
deleteSession,
|
|
14
|
+
refreshSessionDB,
|
|
15
|
+
linkConnectedAccount,
|
|
16
|
+
unlinkConnectedAccount,
|
|
17
|
+
getLinkedAccounts,
|
|
18
|
+
} from "../../libs/supabase.helper";
|
|
19
|
+
import { DatabasePackageError } from "@plyaz/errors";
|
|
20
|
+
import { BaseAuthProviderAdapter } from "../base-auth.adapter";
|
|
21
|
+
|
|
22
|
+
export class CustomAdapter extends BaseAuthProviderAdapter {
|
|
23
|
+
async signIn(
|
|
24
|
+
provider: string,
|
|
25
|
+
credentials: AuthCredentials,
|
|
26
|
+
deviceInfo?: AuthDeviceInfo
|
|
27
|
+
) {
|
|
28
|
+
const user = await findUserByEmailProvider(credentials.email, provider);
|
|
29
|
+
if (!user) throw new Error("User not found");
|
|
30
|
+
if (!user.isActive || user.isSuspended)
|
|
31
|
+
throw new Error("User not active or suspended");
|
|
32
|
+
|
|
33
|
+
await updateLastLogin(user.id);
|
|
34
|
+
|
|
35
|
+
const session = await createUserSession(
|
|
36
|
+
user.id,
|
|
37
|
+
deviceInfo ?? {
|
|
38
|
+
ip: "",
|
|
39
|
+
browser: "",
|
|
40
|
+
os: "",
|
|
41
|
+
userAgent: "",
|
|
42
|
+
}
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return { user, session, tokens: { accessToken: session.accessToken } };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async signUp(
|
|
49
|
+
provider: string,
|
|
50
|
+
credentials: AuthCredentials,
|
|
51
|
+
data?: Partial<AuthUser>,
|
|
52
|
+
deviceInfo?: AuthDeviceInfo
|
|
53
|
+
) {
|
|
54
|
+
const userFindByEmail = await findUserByEmailProvider(data?.email ?? "", provider);
|
|
55
|
+
if (userFindByEmail) {
|
|
56
|
+
throw new DatabasePackageError("User already register");
|
|
57
|
+
}
|
|
58
|
+
const user = await createUser(
|
|
59
|
+
credentials.email,
|
|
60
|
+
provider,
|
|
61
|
+
data,
|
|
62
|
+
credentials.password
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const session = await createUserSession(
|
|
66
|
+
user.id,
|
|
67
|
+
deviceInfo ?? {
|
|
68
|
+
ip: "",
|
|
69
|
+
browser: "",
|
|
70
|
+
os: "",
|
|
71
|
+
userAgent: "",
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return { user, session, tokens: { accessToken: session.accessToken } };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async signOut(sessionId: string) {
|
|
79
|
+
await deleteSession(sessionId);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async getSession(sessionId: string) {
|
|
83
|
+
return await getSession(sessionId);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async validateSession(sessionId: string) {
|
|
87
|
+
return await validateSessionDB(sessionId);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async refreshSession(sessionId: string) {
|
|
91
|
+
const session = await refreshSessionDB(sessionId);
|
|
92
|
+
return { session, tokens: { accessToken: session.accessToken } };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async getOAuthUrl(provider: string, redirectUri: string) {
|
|
96
|
+
return `https://provider/oauth?redirect_uri=${redirectUri}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async handleOAuthCallback(provider: string, code: string) {
|
|
100
|
+
globalThis.console.log(provider,code)
|
|
101
|
+
return { providerAccountId: "id", profile: {} };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async linkAccount(
|
|
105
|
+
userId: string,
|
|
106
|
+
provider: AUTHPROVIDER,
|
|
107
|
+
data: ConnectedAccount
|
|
108
|
+
) : Promise<ConnectedAccount | void> {
|
|
109
|
+
return await linkConnectedAccount(userId, provider, data);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async unlinkAccount(userId: string, accountId: string) {
|
|
113
|
+
await unlinkConnectedAccount(userId, accountId);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async getLinkedAccounts(userId: string) {
|
|
117
|
+
return await getLinkedAccounts(userId);
|
|
118
|
+
}
|
|
119
|
+
}
|