|
1 | | -import type { |
2 | | - BuiltInProviderType, |
3 | | - RedirectableProviderType, |
4 | | -} from '@auth/core/providers' |
5 | | -import type { |
6 | | - AuthClientConfig, |
7 | | - ClientSafeProvider, |
8 | | - LiteralUnion, |
9 | | - SignInAuthorizationParams, |
10 | | - SignInOptions, |
11 | | - SignInResponse, |
12 | | - SignOutParams, |
13 | | - SignOutResponse, |
14 | | -} from './client' |
15 | | - |
16 | | -import { fetchData, parseUrl } from './client' |
17 | | - |
18 | | -class AuthConfigManager { |
19 | | - private static instance: AuthConfigManager | null = null |
20 | | - _config: AuthClientConfig = { |
21 | | - baseUrl: |
22 | | - typeof window !== 'undefined' |
23 | | - ? parseUrl(window.location.origin).origin |
24 | | - : '', |
25 | | - basePath: |
26 | | - typeof window !== 'undefined' |
27 | | - ? parseUrl(window.location.origin).path |
28 | | - : '/api/auth', |
29 | | - credentials: 'same-origin', |
30 | | - _lastSync: 0, |
31 | | - _session: undefined, |
32 | | - _getSession: () => {}, |
33 | | - } |
34 | | - |
35 | | - static getInstance(): AuthConfigManager { |
36 | | - if (!AuthConfigManager.instance) { |
37 | | - AuthConfigManager.instance = new AuthConfigManager() |
38 | | - } |
39 | | - return AuthConfigManager.instance |
40 | | - } |
41 | | - |
42 | | - setConfig(userConfig: Partial<AuthClientConfig>): void { |
43 | | - this._config = { ...this._config, ...userConfig } |
44 | | - } |
45 | | - |
46 | | - getConfig(): AuthClientConfig { |
47 | | - return this._config |
48 | | - } |
49 | | -} |
50 | | - |
51 | | -export const authConfigManager = AuthConfigManager.getInstance() |
52 | | -type ProvidersType = Record< |
53 | | - LiteralUnion<BuiltInProviderType>, |
54 | | - ClientSafeProvider |
55 | | -> |
56 | | - |
57 | | -export async function getProviders() { |
58 | | - return fetchData<ProvidersType>('providers', authConfigManager.getConfig()) |
59 | | -} |
60 | | - |
61 | | -export async function getCsrfToken() { |
62 | | - const response = await fetchData<{ csrfToken: string }>( |
63 | | - 'csrf', |
64 | | - authConfigManager.getConfig(), |
65 | | - ) |
66 | | - return response?.csrfToken ?? '' |
67 | | -} |
68 | | - |
69 | | -export async function signIn< |
70 | | - P extends RedirectableProviderType | undefined = undefined, |
71 | | ->( |
72 | | - provider?: LiteralUnion< |
73 | | - P extends RedirectableProviderType |
74 | | - ? P | BuiltInProviderType |
75 | | - : BuiltInProviderType |
76 | | - >, |
77 | | - options?: SignInOptions, |
78 | | - authorizationParams?: SignInAuthorizationParams, |
79 | | -): Promise< |
80 | | - P extends RedirectableProviderType ? SignInResponse | undefined : undefined |
81 | | -> { |
82 | | - const { callbackUrl = window.location.href, redirect = true } = options ?? {} |
83 | | - |
84 | | - const __AUTHJS: AuthClientConfig = authConfigManager.getConfig() |
85 | | - |
86 | | - const href = `${__AUTHJS.baseUrl}${__AUTHJS.basePath}` |
87 | | - |
88 | | - const providers = await getProviders() |
89 | | - |
90 | | - if (!providers) { |
91 | | - window.location.href = `${href}/error` |
92 | | - return |
93 | | - } |
94 | | - |
95 | | - if (!provider || !(provider in providers)) { |
96 | | - window.location.href = `${href}/signin?${new URLSearchParams({ |
97 | | - callbackUrl, |
98 | | - })}` |
99 | | - return |
100 | | - } |
101 | | - |
102 | | - const isCredentials = providers[provider].type === 'credentials' |
103 | | - const isEmail = providers[provider].type === 'email' |
104 | | - const isSupportingReturn = isCredentials || isEmail |
105 | | - |
106 | | - const signInUrl = `${href}/${ |
107 | | - isCredentials ? 'callback' : 'signin' |
108 | | - }/${provider}` |
109 | | - |
110 | | - const csrfToken = await getCsrfToken() |
111 | | - const res = await fetch( |
112 | | - `${signInUrl}?${new URLSearchParams(authorizationParams)}`, |
113 | | - { |
114 | | - method: 'post', |
115 | | - headers: { |
116 | | - 'Content-Type': 'application/x-www-form-urlencoded', |
117 | | - 'X-Auth-Return-Redirect': '1', |
118 | | - }, |
119 | | - // @ts-expect-error TODO: Fix this |
120 | | - body: new URLSearchParams({ ...options, csrfToken, callbackUrl }), |
121 | | - credentials: __AUTHJS.credentials, |
122 | | - }, |
123 | | - ) |
124 | | - |
125 | | - const data = await res.json() |
126 | | - |
127 | | - // TODO: Do not redirect for Credentials and Email providers by default in next major |
128 | | - if (redirect || !isSupportingReturn) { |
129 | | - const url = (data as any).url ?? callbackUrl |
130 | | - window.location.href = url |
131 | | - // If url contains a hash, the browser does not reload the page. We reload manually |
132 | | - if (url.includes('#')) { |
133 | | - window.location.reload() |
134 | | - } |
135 | | - return |
136 | | - } |
137 | | - |
138 | | - const error = new URL((data as any).url).searchParams.get('error') |
139 | | - |
140 | | - if (res.ok) { |
141 | | - await __AUTHJS._getSession({ event: 'storage' }) |
142 | | - } |
143 | | - |
144 | | - return { |
145 | | - error, |
146 | | - status: res.status, |
147 | | - ok: res.ok, |
148 | | - url: error ? null : (data as any).url, |
149 | | - } as any |
150 | | -} |
151 | | - |
152 | | -/** |
153 | | - * Initiate a signout, by destroying the current session. |
154 | | - * Handles CSRF protection. |
155 | | - */ |
156 | | -export async function signOut<R extends boolean = true>( |
157 | | - options?: SignOutParams<R>, |
158 | | -): Promise<R extends true ? undefined : SignOutResponse> { |
159 | | - const { callbackUrl = window.location.href } = options ?? {} |
160 | | - const __AUTHJS: AuthClientConfig = authConfigManager.getConfig() |
161 | | - const href = `${__AUTHJS.baseUrl}${__AUTHJS.basePath}` |
162 | | - const csrfToken = await getCsrfToken() |
163 | | - const res = await fetch(`${href}/signout`, { |
164 | | - method: 'post', |
165 | | - headers: { |
166 | | - 'Content-Type': 'application/x-www-form-urlencoded', |
167 | | - 'X-Auth-Return-Redirect': '1', |
168 | | - }, |
169 | | - body: new URLSearchParams({ csrfToken, callbackUrl }), |
170 | | - credentials: __AUTHJS.credentials, |
171 | | - }) |
172 | | - const data = await res.json() |
173 | | - |
174 | | - if (options?.redirect ?? true) { |
175 | | - const url = (data as any).url ?? callbackUrl |
176 | | - window.location.href = url |
177 | | - // If url contains a hash, the browser does not reload the page. We reload manually |
178 | | - if (url.includes('#')) { |
179 | | - window.location.reload() |
180 | | - } |
181 | | - // @ts-expect-error TODO: Fix this |
182 | | - return |
183 | | - } |
184 | | - |
185 | | - await __AUTHJS._getSession({ event: 'storage' }) |
186 | | - |
187 | | - return data as any |
188 | | -} |
| 1 | +import { createAuthClient } from 'better-auth/client' |
| 2 | + |
| 3 | +import { API_URL } from '~/constants/env' |
| 4 | + |
| 5 | +export const authClient = createAuthClient({ |
| 6 | + baseURL: API_URL + '/auth', |
| 7 | + fetchOptions: { |
| 8 | + credentials: 'include', |
| 9 | + }, |
| 10 | +}) |
| 11 | + |
| 12 | +export type AuthSocialProviders = |
| 13 | + | 'apple' |
| 14 | + | 'discord' |
| 15 | + | 'facebook' |
| 16 | + | 'github' |
| 17 | + | 'google' |
| 18 | + | 'microsoft' |
| 19 | + | 'spotify' |
| 20 | + | 'twitch' |
| 21 | + | 'twitter' |
| 22 | + | 'dropbox' |
| 23 | + | 'linkedin' |
| 24 | + | 'gitlab' |
0 commit comments