@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.
Files changed (113) hide show
  1. package/.github/pull_request_template.md +71 -0
  2. package/.github/workflows/deploy.yml +9 -0
  3. package/.github/workflows/publish.yml +14 -0
  4. package/.github/workflows/security.yml +20 -0
  5. package/README.md +89 -0
  6. package/commits.txt +5 -0
  7. package/dist/common/index.cjs +48 -0
  8. package/dist/common/index.cjs.map +1 -0
  9. package/dist/common/index.mjs +43 -0
  10. package/dist/common/index.mjs.map +1 -0
  11. package/dist/index.cjs +20411 -0
  12. package/dist/index.cjs.map +1 -0
  13. package/dist/index.mjs +5139 -0
  14. package/dist/index.mjs.map +1 -0
  15. package/eslint.config.mjs +13 -0
  16. package/index.html +13 -0
  17. package/package.json +141 -0
  18. package/src/adapters/auth-adapter-factory.ts +26 -0
  19. package/src/adapters/auth-adapter.mapper.ts +53 -0
  20. package/src/adapters/base-auth.adapter.ts +119 -0
  21. package/src/adapters/clerk/clerk.adapter.ts +204 -0
  22. package/src/adapters/custom/custom.adapter.ts +119 -0
  23. package/src/adapters/index.ts +4 -0
  24. package/src/adapters/next-auth/authOptions.ts +81 -0
  25. package/src/adapters/next-auth/next-auth.adapter.ts +211 -0
  26. package/src/api/client.ts +37 -0
  27. package/src/audit/audit.logger.ts +52 -0
  28. package/src/client/components/ProtectedRoute.tsx +37 -0
  29. package/src/client/hooks/useAuth.ts +128 -0
  30. package/src/client/hooks/useConnectedAccounts.ts +108 -0
  31. package/src/client/hooks/usePermissions.ts +36 -0
  32. package/src/client/hooks/useRBAC.ts +36 -0
  33. package/src/client/hooks/useSession.ts +18 -0
  34. package/src/client/providers/AuthProvider.tsx +104 -0
  35. package/src/client/store/auth.store.ts +306 -0
  36. package/src/client/utils/storage.ts +70 -0
  37. package/src/common/constants/oauth-providers.ts +49 -0
  38. package/src/common/errors/auth.errors.ts +64 -0
  39. package/src/common/errors/specific-auth-errors.ts +201 -0
  40. package/src/common/index.ts +19 -0
  41. package/src/common/regex/index.ts +27 -0
  42. package/src/common/types/auth.types.ts +641 -0
  43. package/src/common/types/index.ts +297 -0
  44. package/src/common/utils/index.ts +84 -0
  45. package/src/core/blacklist/token.blacklist.ts +60 -0
  46. package/src/core/index.ts +2 -0
  47. package/src/core/jwt/jwt.manager.ts +131 -0
  48. package/src/core/session/session.manager.ts +56 -0
  49. package/src/db/repositories/connected-account.repository.ts +415 -0
  50. package/src/db/repositories/role.repository.ts +519 -0
  51. package/src/db/repositories/session.repository.ts +308 -0
  52. package/src/db/repositories/user.repository.ts +320 -0
  53. package/src/flows/index.ts +2 -0
  54. package/src/flows/sign-in.flow.ts +106 -0
  55. package/src/flows/sign-up.flow.ts +121 -0
  56. package/src/index.ts +54 -0
  57. package/src/libs/clerk.helper.ts +36 -0
  58. package/src/libs/supabase.helper.ts +255 -0
  59. package/src/libs/supabaseClient.ts +6 -0
  60. package/src/providers/base/auth-provider.interface.ts +42 -0
  61. package/src/providers/base/index.ts +1 -0
  62. package/src/providers/index.ts +2 -0
  63. package/src/providers/oauth/facebook.provider.ts +97 -0
  64. package/src/providers/oauth/github.provider.ts +148 -0
  65. package/src/providers/oauth/google.provider.ts +126 -0
  66. package/src/providers/oauth/index.ts +3 -0
  67. package/src/rbac/dynamic-roles.ts +552 -0
  68. package/src/rbac/index.ts +4 -0
  69. package/src/rbac/permission-checker.ts +464 -0
  70. package/src/rbac/role-hierarchy.ts +545 -0
  71. package/src/rbac/role.manager.ts +75 -0
  72. package/src/security/csrf/csrf.protection.ts +37 -0
  73. package/src/security/index.ts +3 -0
  74. package/src/security/rate-limiting/auth/auth.controller.ts +12 -0
  75. package/src/security/rate-limiting/auth/rate-limiting.interface.ts +67 -0
  76. package/src/security/rate-limiting/auth.module.ts +32 -0
  77. package/src/server/auth.module.ts +158 -0
  78. package/src/server/decorators/auth.decorator.ts +43 -0
  79. package/src/server/decorators/auth.decorators.ts +31 -0
  80. package/src/server/decorators/current-user.decorator.ts +49 -0
  81. package/src/server/decorators/permission.decorator.ts +49 -0
  82. package/src/server/guards/auth.guard.ts +56 -0
  83. package/src/server/guards/custom-throttler.guard.ts +46 -0
  84. package/src/server/guards/permissions.guard.ts +115 -0
  85. package/src/server/guards/roles.guard.ts +31 -0
  86. package/src/server/middleware/auth.middleware.ts +46 -0
  87. package/src/server/middleware/index.ts +2 -0
  88. package/src/server/middleware/middleware.ts +11 -0
  89. package/src/server/middleware/session.middleware.ts +255 -0
  90. package/src/server/services/account.service.ts +269 -0
  91. package/src/server/services/auth.service.ts +79 -0
  92. package/src/server/services/brute-force.service.ts +98 -0
  93. package/src/server/services/index.ts +15 -0
  94. package/src/server/services/rate-limiter.service.ts +60 -0
  95. package/src/server/services/session.service.ts +287 -0
  96. package/src/server/services/token.service.ts +262 -0
  97. package/src/session/cookie-store.ts +255 -0
  98. package/src/session/enhanced-session-manager.ts +406 -0
  99. package/src/session/index.ts +14 -0
  100. package/src/session/memory-store.ts +320 -0
  101. package/src/session/redis-store.ts +443 -0
  102. package/src/strategies/oauth.strategy.ts +128 -0
  103. package/src/strategies/traditional-auth.strategy.ts +116 -0
  104. package/src/tokens/index.ts +4 -0
  105. package/src/tokens/refresh-token-manager.ts +448 -0
  106. package/src/tokens/token-validator.ts +311 -0
  107. package/tsconfig.build.json +28 -0
  108. package/tsconfig.json +38 -0
  109. package/tsup.config.mjs +28 -0
  110. package/vitest.config.mjs +16 -0
  111. package/vitest.setup.d.ts +2 -0
  112. package/vitest.setup.d.ts.map +1 -0
  113. 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
+ }
@@ -0,0 +1,4 @@
1
+ export { ClerkAdapter } from "./clerk/clerk.adapter";
2
+ export { NextAuthAdapter } from "./next-auth/next-auth.adapter";
3
+ export { CustomAdapter } from "./custom/custom.adapter";
4
+