hightjs 0.2.41 → 0.2.43

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.
@@ -0,0 +1,218 @@
1
+ import type {AuthProviderClass, AuthRoute, User} from '../types';
2
+ import {HightJSRequest, HightJSResponse} from '../../api/http';
3
+
4
+ export interface GoogleConfig {
5
+ id?: string;
6
+ name?: string;
7
+ clientId: string;
8
+ clientSecret: string;
9
+ callbackUrl?: string;
10
+ successUrl?: string;
11
+ // Escopos OAuth do Google, padrão: ['openid', 'email', 'profile']
12
+ scope?: string[];
13
+ }
14
+
15
+ /**
16
+ * Provider para autenticação com Google OAuth2
17
+ *
18
+ * Este provider permite autenticação usando Google OAuth2.
19
+ * Automaticamente gerencia o fluxo OAuth completo e rotas necessárias.
20
+ *
21
+ * Exemplo de uso:
22
+ * ```typescript
23
+ * new GoogleProvider({
24
+ * clientId: process.env.GOOGLE_CLIENT_ID!,
25
+ * clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
26
+ * callbackUrl: "http://localhost:3000/api/auth/callback/google"
27
+ * })
28
+ * ```
29
+ *
30
+ * Fluxo de autenticação:
31
+ * 1. GET /api/auth/signin/google - Gera URL e redireciona para Google
32
+ * 2. Google redireciona para /api/auth/callback/google com código
33
+ * 3. Provider troca código por token e busca dados do usuário
34
+ * 4. Retorna objeto User com dados do Google
35
+ */
36
+ export class GoogleProvider implements AuthProviderClass {
37
+ public readonly id: string;
38
+ public readonly name: string;
39
+ public readonly type: string = 'google';
40
+
41
+ private config: GoogleConfig;
42
+ private readonly defaultScope = [
43
+ 'openid',
44
+ 'https://www.googleapis.com/auth/userinfo.email',
45
+ 'https://www.googleapis.com/auth/userinfo.profile'
46
+ ];
47
+
48
+ constructor(config: GoogleConfig) {
49
+ this.config = config;
50
+ this.id = config.id || 'google';
51
+ this.name = config.name || 'Google';
52
+ }
53
+
54
+ /**
55
+ * Método para gerar URL OAuth (usado pelo handleSignIn)
56
+ */
57
+ handleOauth(credentials: Record<string, string> = {}): string {
58
+ return this.getAuthorizationUrl();
59
+ }
60
+
61
+ /**
62
+ * Método principal - redireciona para OAuth ou processa o callback
63
+ */
64
+ async handleSignIn(credentials: Record<string, string>): Promise<User | string | null> {
65
+ // Se tem código, é o callback - processa a autenticação
66
+ if (credentials.code) {
67
+ return await this.processOAuthCallback(credentials);
68
+ }
69
+
70
+ // Se não tem código, é o início do OAuth - retorna a URL
71
+ return this.handleOauth(credentials);
72
+ }
73
+
74
+ /**
75
+ * Processa o callback do OAuth (troca o código pelo usuário)
76
+ */
77
+ private async processOAuthCallback(credentials: Record<string, string>): Promise<User | null> {
78
+ try {
79
+ const { code } = credentials;
80
+ if (!code) {
81
+ throw new Error('Authorization code not provided');
82
+ }
83
+
84
+ // Troca o código por um access token
85
+ const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
86
+ method: 'POST',
87
+ headers: {
88
+ 'Content-Type': 'application/x-www-form-urlencoded',
89
+ },
90
+ body: new URLSearchParams({
91
+ client_id: this.config.clientId,
92
+ client_secret: this.config.clientSecret,
93
+ grant_type: 'authorization_code',
94
+ code,
95
+ redirect_uri: this.config.callbackUrl || '',
96
+ }),
97
+ });
98
+
99
+ if (!tokenResponse.ok) {
100
+ const error = await tokenResponse.text();
101
+ throw new Error(`Failed to exchange code for token: ${error}`);
102
+ }
103
+
104
+ const tokens = await tokenResponse.json();
105
+
106
+ // Busca os dados do usuário com o access token
107
+ const userResponse = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
108
+ headers: {
109
+ 'Authorization': `Bearer ${tokens.access_token}`,
110
+ },
111
+ });
112
+
113
+ if (!userResponse.ok) {
114
+ throw new Error('Failed to fetch user data');
115
+ }
116
+
117
+ const googleUser = await userResponse.json();
118
+
119
+ // Retorna o objeto User padronizado
120
+ return {
121
+ id: googleUser.id,
122
+ name: googleUser.name,
123
+ email: googleUser.email,
124
+ image: googleUser.picture || null,
125
+ provider: this.id,
126
+ providerId: googleUser.id,
127
+ accessToken: tokens.access_token,
128
+ refreshToken: tokens.refresh_token
129
+ };
130
+
131
+ } catch (error) {
132
+ console.error(`[${this.id} Provider] Error during OAuth callback:`, error);
133
+ return null;
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Rotas adicionais específicas do Google OAuth
139
+ */
140
+ public additionalRoutes: AuthRoute[] = [
141
+ // Rota de callback do Google
142
+ {
143
+ method: 'GET',
144
+ path: '/api/auth/callback/google',
145
+ handler: async (req: HightJSRequest, params: any) => {
146
+ const url = new URL(req.url || '', 'http://localhost');
147
+ const code = url.searchParams.get('code');
148
+
149
+ if (!code) {
150
+ return HightJSResponse.json({ error: 'Authorization code not provided' }, { status: 400 });
151
+ }
152
+
153
+ try {
154
+ // Delega o 'code' para o endpoint de signin principal
155
+ const authResponse = await fetch(`${req.headers.origin || 'http://localhost:3000'}/api/auth/signin`, {
156
+ method: 'POST',
157
+ headers: {
158
+ 'Content-Type': 'application/json',
159
+ },
160
+ body: JSON.stringify({
161
+ provider: this.id,
162
+ code,
163
+ })
164
+ });
165
+
166
+ if (authResponse.ok) {
167
+ // Propaga o cookie de sessão e redireciona para a URL de sucesso
168
+ const setCookieHeader = authResponse.headers.get('set-cookie');
169
+
170
+ if(this.config.successUrl) {
171
+ return HightJSResponse
172
+ .redirect(this.config.successUrl)
173
+ .header('Set-Cookie', setCookieHeader || '');
174
+ }
175
+ return HightJSResponse.json({ success: true })
176
+ .header('Set-Cookie', setCookieHeader || '');
177
+ } else {
178
+ const errorText = await authResponse.text();
179
+ console.error(`[${this.id} Provider] Session creation failed during callback. Status: ${authResponse.status}, Body: ${errorText}`);
180
+ return HightJSResponse.json({ error: 'Session creation failed' }, { status: 500 });
181
+ }
182
+
183
+ } catch (error) {
184
+ console.error(`[${this.id} Provider] Callback handler fetch error:`, error);
185
+ return HightJSResponse.json({ error: 'Internal server error' }, { status: 500 });
186
+ }
187
+ }
188
+ }
189
+ ];
190
+
191
+ /**
192
+ * Gera a URL de autorização do Google
193
+ */
194
+ getAuthorizationUrl(): string {
195
+ const params = new URLSearchParams({
196
+ client_id: this.config.clientId,
197
+ redirect_uri: this.config.callbackUrl || '',
198
+ response_type: 'code',
199
+ scope: (this.config.scope || this.defaultScope).join(' ')
200
+ });
201
+
202
+ return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
203
+ }
204
+
205
+ /**
206
+ * Retorna a configuração pública do provider
207
+ */
208
+ getConfig(): any {
209
+ return {
210
+ id: this.id,
211
+ name: this.name,
212
+ type: this.type,
213
+ clientId: this.config.clientId, // Público
214
+ scope: this.config.scope || this.defaultScope,
215
+ callbackUrl: this.config.callbackUrl
216
+ };
217
+ }
218
+ }
@@ -1,4 +1,4 @@
1
1
  // Exportações dos providers
2
2
  export { CredentialsProvider } from './providers/credentials';
3
3
  export { DiscordProvider } from './providers/discord';
4
-
4
+ export { GoogleProvider } from './providers/google';
package/src/auth/types.ts CHANGED
@@ -63,7 +63,7 @@ export interface AuthConfig {
63
63
  };
64
64
  callbacks?: {
65
65
  signIn?: (user: User, account: any, profile: any) => boolean | Promise<boolean>;
66
- session?: (session: Session, user: User) => Session | Promise<Session>;
66
+ session?: ({session, user, provider}: {session: Session, user: User, provider: string}) => Session | Promise<Session>;
67
67
  jwt?: (token: any, user: User, account: any, profile: any) => any | Promise<any>;
68
68
  };
69
69
  session?: {
@@ -73,15 +73,10 @@ export interface AuthConfig {
73
73
  };
74
74
  secret?: string;
75
75
  debug?: boolean;
76
+ secureCookies?: boolean;
76
77
  }
77
78
 
78
- // Interface legada para compatibilidade
79
- export interface AuthProvider {
80
- id: string;
81
- name: string;
82
- type: 'credentials';
83
- authorize?: (credentials: Record<string, string>) => Promise<User | null> | User | null;
84
- }
79
+
85
80
 
86
81
  // Provider para credenciais
87
82
  export interface CredentialsConfig {
@@ -26,7 +26,17 @@ function App({ componentMap, routes, initialComponentPath, initialParams, layout
26
26
 
27
27
  const findRouteForPath = useCallback((path: string) => {
28
28
  for (const route of routes) {
29
- const regexPattern = route.pattern.replace(/\[(\w+)\]/g, '(?<$1>[^/]+)');
29
+ const regexPattern = route.pattern
30
+ // [[...param]] → opcional catch-all
31
+ .replace(/\[\[\.\.\.(\w+)\]\]/g, '(?<$1>.+)?')
32
+ // [...param] → obrigatório catch-all
33
+ .replace(/\[\.\.\.(\w+)\]/g, '(?<$1>.+)')
34
+ // /[[param]] → opcional com barra também opcional
35
+ .replace(/\/\[\[(\w+)\]\]/g, '(?:/(?<$1>[^/]+))?')
36
+ // [[param]] → segmento opcional (sem barra anterior)
37
+ .replace(/\[\[(\w+)\]\]/g, '(?<$1>[^/]+)?')
38
+ // [param] → segmento obrigatório
39
+ .replace(/\[(\w+)\]/g, '(?<$1>[^/]+)');
30
40
  const regex = new RegExp(`^${regexPattern}/?$`);
31
41
  const match = path.match(regex);
32
42
  if (match) {
@@ -300,3 +310,4 @@ if (document.readyState === 'loading') {
300
310
  } else {
301
311
  initializeClient();
302
312
  }
313
+
@@ -0,0 +1 @@
1
+