hightjs 0.1.1 → 0.2.1

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.
@@ -6,9 +6,6 @@ export function setBasePath(path: string) {
6
6
  basePath = path;
7
7
  }
8
8
 
9
-
10
-
11
-
12
9
  /**
13
10
  * Função para obter a sessão atual (similar ao NextAuth getSession)
14
11
  */
@@ -72,3 +69,87 @@ export async function getProviders(): Promise<any[] | null> {
72
69
  }
73
70
  }
74
71
 
72
+ /**
73
+ * Função para fazer login (similar ao NextAuth signIn)
74
+ */
75
+ export async function signIn(
76
+ provider: string = 'credentials',
77
+ options: SignInOptions = {}
78
+ ): Promise<SignInResult | undefined> {
79
+ try {
80
+ const { redirect = true, callbackUrl, ...credentials } = options;
81
+
82
+ const response = await fetch(`${basePath}/signin`, {
83
+ method: 'POST',
84
+ headers: {
85
+ 'Content-Type': 'application/json',
86
+ },
87
+ credentials: 'include',
88
+ body: JSON.stringify({
89
+ provider,
90
+ ...credentials
91
+ })
92
+ });
93
+
94
+ const data = await response.json();
95
+
96
+ if (response.ok && data.success) {
97
+ // Se é OAuth, redireciona para URL fornecida
98
+ if (data.type === 'oauth' && data.redirectUrl) {
99
+ if (redirect && typeof window !== 'undefined') {
100
+ window.location.href = data.redirectUrl;
101
+ }
102
+
103
+ return {
104
+ ok: true,
105
+ status: 200,
106
+ url: data.redirectUrl
107
+ };
108
+ }
109
+
110
+ // Se é sessão (credentials), redireciona para callbackUrl
111
+ if (data.type === 'session') {
112
+ if (redirect && typeof window !== 'undefined') {
113
+ window.location.href = callbackUrl || '/';
114
+ }
115
+
116
+ return {
117
+ ok: true,
118
+ status: 200,
119
+ url: callbackUrl || '/'
120
+ };
121
+ }
122
+ } else {
123
+ return {
124
+ error: data.error || 'Authentication failed',
125
+ status: response.status,
126
+ ok: false
127
+ };
128
+ }
129
+ } catch (error) {
130
+ console.error('[hweb-auth] Erro no signIn:', error);
131
+ return {
132
+ error: 'Network error',
133
+ status: 500,
134
+ ok: false
135
+ };
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Função para fazer logout (similar ao NextAuth signOut)
141
+ */
142
+ export async function signOut(options: { callbackUrl?: string } = {}): Promise<void> {
143
+ try {
144
+ await fetch(`${basePath}/signout`, {
145
+ method: 'POST',
146
+ credentials: 'include'
147
+ });
148
+
149
+ if (typeof window !== 'undefined') {
150
+ window.location.href = options.callbackUrl || '/';
151
+ }
152
+ } catch (error) {
153
+ console.error('[hweb-auth] Erro no signOut:', error);
154
+ }
155
+ }
package/src/auth/core.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { HightJSRequest, HightJSResponse } from '../api/http';
2
- import type { AuthConfig, AuthProvider, User, Session } from './types';
2
+ import type { AuthConfig, AuthProviderClass, User, Session } from './types';
3
3
  import { SessionManager } from './jwt';
4
4
 
5
5
  export class HWebAuth {
@@ -37,38 +37,45 @@ export class HWebAuth {
37
37
  }
38
38
 
39
39
  /**
40
- * Autentica um usuário com credenciais
40
+ * Autentica um usuário usando um provider específico
41
41
  */
42
- async signIn(provider: string, credentials: Record<string, string>): Promise<{ session: Session; token: string } | null> {
43
- const authProvider = this.config.providers.find(p => p.id === provider);
44
- if (!authProvider || authProvider.type !== 'credentials') {
45
- return null;
46
- }
47
-
48
- if (!authProvider.authorize) {
42
+ async signIn(providerId: string, credentials: Record<string, string>): Promise<{ session: Session; token: string } | { redirectUrl: string } | null> {
43
+ const provider = this.config.providers.find(p => p.id === providerId);
44
+ if (!provider) {
45
+ console.error(`[hweb-auth] Provider not found: ${providerId}`);
49
46
  return null;
50
47
  }
51
48
 
52
49
  try {
53
- const user = await authProvider.authorize(credentials);
54
- if (!user) return null;
50
+ // Usa o método handleSignIn do provider
51
+ const result = await provider.handleSignIn(credentials);
52
+
53
+ if (!result) return null;
54
+
55
+ // Se resultado é string, é URL de redirecionamento OAuth
56
+ if (typeof result === 'string') {
57
+ return { redirectUrl: result };
58
+ }
59
+
60
+ // Se resultado é User, cria sessão
61
+ const user = result as User;
55
62
 
56
63
  // Callback de signIn se definido
57
64
  if (this.config.callbacks?.signIn) {
58
- const allowed = await this.config.callbacks.signIn(user, { provider }, {});
65
+ const allowed = await this.config.callbacks.signIn(user, { provider: providerId }, {});
59
66
  if (!allowed) return null;
60
67
  }
61
68
 
62
- const result = this.sessionManager.createSession(user);
69
+ const sessionResult = this.sessionManager.createSession(user);
63
70
 
64
71
  // Callback de sessão se definido
65
72
  if (this.config.callbacks?.session) {
66
- result.session = await this.config.callbacks.session(result.session, user);
73
+ sessionResult.session = await this.config.callbacks.session(sessionResult.session, user);
67
74
  }
68
75
 
69
- return result;
76
+ return sessionResult;
70
77
  } catch (error) {
71
- console.error('[hweb-auth] Erro no signIn:', error);
78
+ console.error(`[hweb-auth] Erro no signIn com provider ${providerId}:`, error);
72
79
  return null;
73
80
  }
74
81
  }
@@ -76,14 +83,28 @@ export class HWebAuth {
76
83
  /**
77
84
  * Faz logout do usuário
78
85
  */
79
- signOut(): HightJSResponse {
86
+ async signOut(req: HightJSRequest): Promise<HightJSResponse> {
87
+ // Busca a sessão atual para saber qual provider usar
88
+ const { session } = await this.middleware(req);
89
+
90
+ if (session?.user?.provider) {
91
+ const provider = this.config.providers.find(p => p.id === session.user.provider);
92
+ if (provider && provider.handleSignOut) {
93
+ try {
94
+ await provider.handleSignOut();
95
+ } catch (error) {
96
+ console.error(`[hweb-auth] Erro no signOut do provider ${provider.id}:`, error);
97
+ }
98
+ }
99
+ }
100
+
80
101
  return HightJSResponse
81
102
  .json({ success: true })
82
103
  .clearCookie('hweb-auth-token', {
83
104
  path: '/',
84
105
  httpOnly: true,
85
- secure: true, // Always use secure cookies
86
- sameSite: 'strict' // Stronger CSRF protection
106
+ secure: true,
107
+ sameSite: 'strict'
87
108
  });
88
109
  }
89
110
 
@@ -103,6 +124,41 @@ export class HWebAuth {
103
124
  return session !== null;
104
125
  }
105
126
 
127
+ /**
128
+ * Retorna todos os providers disponíveis (dados públicos)
129
+ */
130
+ getProviders(): any[] {
131
+ return this.config.providers.map(provider => ({
132
+ id: provider.id,
133
+ name: provider.name,
134
+ type: provider.type,
135
+ config: provider.getConfig ? provider.getConfig() : {}
136
+ }));
137
+ }
138
+
139
+ /**
140
+ * Busca um provider específico
141
+ */
142
+ getProvider(id: string): AuthProviderClass | null {
143
+ return this.config.providers.find(p => p.id === id) || null;
144
+ }
145
+
146
+ /**
147
+ * Retorna todas as rotas adicionais dos providers
148
+ */
149
+ getAllAdditionalRoutes(): Array<{ provider: string; route: any }> {
150
+ const routes: Array<{ provider: string; route: any }> = [];
151
+
152
+ for (const provider of this.config.providers) {
153
+ if (provider.additionalRoutes) {
154
+ for (const route of provider.additionalRoutes) {
155
+ routes.push({ provider: provider.id, route });
156
+ }
157
+ }
158
+ }
159
+
160
+ return routes;
161
+ }
106
162
 
107
163
  /**
108
164
  * Cria resposta com cookie de autenticação - Secure implementation
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Exemplo de como usar os novos providers baseados em classes
3
+ */
4
+
5
+ import { createAuthRoutes } from './routes';
6
+ import { CredentialsProvider, DiscordProvider } from './providers';
7
+ import type { AuthConfig } from './types';
8
+
9
+ // Exemplo de configuração com os novos providers
10
+ const authConfig: AuthConfig = {
11
+ providers: [
12
+ // Provider de credenciais customizado
13
+ new CredentialsProvider({
14
+ name: "Login com Email",
15
+ credentials: {
16
+ email: {
17
+ label: "Email",
18
+ type: "email",
19
+ placeholder: "seu@email.com"
20
+ },
21
+ password: {
22
+ label: "Senha",
23
+ type: "password"
24
+ }
25
+ },
26
+ async authorize(credentials) {
27
+ // Aqui você faz a validação com seu banco de dados
28
+ const { email, password } = credentials;
29
+
30
+ // Exemplo de validação (substitua pela sua lógica)
31
+ if (email === "admin@example.com" && password === "123456") {
32
+ return {
33
+ id: "1",
34
+ name: "Admin User",
35
+ email: email,
36
+ role: "admin"
37
+ };
38
+ }
39
+
40
+ // Retorna null se credenciais inválidas
41
+ return null;
42
+ }
43
+ }),
44
+
45
+ // Provider do Discord
46
+ new DiscordProvider({
47
+ clientId: process.env.DISCORD_CLIENT_ID!,
48
+ clientSecret: process.env.DISCORD_CLIENT_SECRET!,
49
+ callbackUrl: "http://localhost:3000/api/auth/callback/discord"
50
+ })
51
+ ],
52
+
53
+ secret: process.env.HWEB_AUTH_SECRET || "seu-super-secret-aqui-32-chars-min",
54
+
55
+ session: {
56
+ strategy: 'jwt',
57
+ maxAge: 86400 // 24 horas
58
+ },
59
+
60
+ pages: {
61
+ signIn: '/auth/signin',
62
+ signOut: '/auth/signout'
63
+ },
64
+
65
+ callbacks: {
66
+ async signIn(user, account, profile) {
67
+ // Lógica customizada antes do login
68
+ console.log(`Usuário ${user.email} fazendo login via ${account.provider}`);
69
+ return true; // permitir login
70
+ },
71
+
72
+ async session(session, user) {
73
+ // Adicionar dados customizados à sessão
74
+ return {
75
+ ...session,
76
+ user: {
77
+ ...session.user,
78
+ customData: "dados extras"
79
+ }
80
+ };
81
+ }
82
+ }
83
+ };
84
+
85
+ // Criar as rotas de autenticação
86
+ export const authRoutes = createAuthRoutes(authConfig);
87
+
88
+ /**
89
+ * Como usar em suas rotas API:
90
+ *
91
+ * // arquivo: /api/auth/[...value].ts
92
+ * import { authRoutes } from '../../../src/auth/example';
93
+ *
94
+ * export const GET = authRoutes.GET;
95
+ * export const POST = authRoutes.POST;
96
+ */
97
+
98
+ /**
99
+ * Rotas disponíveis automaticamente:
100
+ *
101
+ * Core routes:
102
+ * - GET /api/auth/session - Obter sessão atual
103
+ * - GET /api/auth/providers - Listar providers
104
+ * - GET /api/auth/csrf - Obter token CSRF
105
+ * - POST /api/auth/signin - Login
106
+ * - POST /api/auth/signout - Logout
107
+ *
108
+ * Provider específico (CredentialsProvider):
109
+ * - GET /api/auth/credentials/config - Config do provider
110
+ *
111
+ * Provider específico (DiscordProvider):
112
+ * - GET /api/auth/signin/discord - Iniciar OAuth Discord
113
+ * - GET /api/auth/callback/discord - Callback OAuth Discord
114
+ * - GET /api/auth/discord/config - Config do provider Discord
115
+ */
package/src/auth/index.ts CHANGED
@@ -5,5 +5,6 @@ export * from './core';
5
5
  export * from './routes';
6
6
  export * from './jwt';
7
7
 
8
- export { CredentialsProvider } from './providers';
8
+ export { CredentialsProvider, DiscordProvider } from './providers';
9
9
  export { createAuthRoutes } from './routes';
10
+
@@ -0,0 +1,158 @@
1
+ import type { AuthProviderClass, User, AuthRoute } from '../types';
2
+ import { HightJSRequest, HightJSResponse } from '../../api/http';
3
+
4
+ export interface CredentialsConfig {
5
+ id?: string;
6
+ name?: string;
7
+ credentials: Record<string, {
8
+ label: string;
9
+ type: string;
10
+ placeholder?: string;
11
+ }>;
12
+ authorize: (credentials: Record<string, string>) => Promise<User | null> | User | null;
13
+ }
14
+
15
+ /**
16
+ * Provider para autenticação com credenciais (email/senha)
17
+ *
18
+ * Este provider permite autenticação usando email/senha ou qualquer outro
19
+ * sistema de credenciais customizado. Você define a função authorize
20
+ * que será chamada para validar as credenciais.
21
+ *
22
+ * Exemplo de uso:
23
+ * ```typescript
24
+ * new CredentialsProvider({
25
+ * name: "Credentials",
26
+ * credentials: {
27
+ * email: { label: "Email", type: "email" },
28
+ * password: { label: "Password", type: "password" }
29
+ * },
30
+ * async authorize(credentials) {
31
+ * // Aqui você faz a validação com seu banco de dados
32
+ * const user = await validateUser(credentials.email, credentials.password);
33
+ * if (user) {
34
+ * return { id: user.id, name: user.name, email: user.email };
35
+ * }
36
+ * return null;
37
+ * }
38
+ * })
39
+ * ```
40
+ */
41
+ export class CredentialsProvider implements AuthProviderClass {
42
+ public readonly id: string;
43
+ public readonly name: string;
44
+ public readonly type: string = 'credentials';
45
+
46
+ private config: CredentialsConfig;
47
+
48
+ constructor(config: CredentialsConfig) {
49
+ this.config = config;
50
+ this.id = config.id || 'credentials';
51
+ this.name = config.name || 'Credentials';
52
+ }
53
+
54
+ /**
55
+ * Método principal para autenticar usuário com credenciais
56
+ */
57
+ async handleSignIn(credentials: Record<string, string>): Promise<User | null> {
58
+ try {
59
+ if (!this.config.authorize) {
60
+ throw new Error('Authorize function not provided');
61
+ }
62
+
63
+ const user = await this.config.authorize(credentials);
64
+
65
+ if (!user) {
66
+ return null;
67
+ }
68
+
69
+ // Adiciona informações do provider ao usuário
70
+ return {
71
+ ...user,
72
+ provider: this.id,
73
+ providerId: user.id || user.email || 'unknown'
74
+ };
75
+
76
+ } catch (error) {
77
+ console.error(`[${this.id} Provider] Error during sign in:`, error);
78
+ return null;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Método opcional para logout (pode ser sobrescrito se necessário)
84
+ */
85
+ async handleSignOut?(): Promise<void> {
86
+ // Credentials provider não precisa fazer nada específico no logout
87
+ // O core já cuida de limpar cookies e tokens
88
+ console.log(`[${this.id} Provider] User signed out`);
89
+ }
90
+
91
+ /**
92
+ * Rotas adicionais específicas do provider (opcional)
93
+ */
94
+ public additionalRoutes?: AuthRoute[] = [
95
+ {
96
+ method: 'GET',
97
+ path: '/api/auth/credentials/config',
98
+ handler: async (req: HightJSRequest, params: any) => {
99
+ // Retorna configuração das credenciais (sem dados sensíveis)
100
+ const safeConfig = {
101
+ id: this.id,
102
+ name: this.name,
103
+ type: this.type,
104
+ credentials: Object.entries(this.config.credentials).reduce((acc, [key, field]) => {
105
+ acc[key] = {
106
+ label: field.label,
107
+ type: field.type,
108
+ placeholder: field.placeholder
109
+ };
110
+ return acc;
111
+ }, {} as Record<string, any>)
112
+ };
113
+
114
+ return HightJSResponse.json({ config: safeConfig });
115
+ }
116
+ }
117
+ ];
118
+
119
+ /**
120
+ * Retorna configuração pública do provider
121
+ */
122
+ getConfig(): any {
123
+ return {
124
+ id: this.id,
125
+ name: this.name,
126
+ type: this.type,
127
+ credentials: this.config.credentials
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Valida se as credenciais fornecidas são válidas
133
+ */
134
+ validateCredentials(credentials: Record<string, string>): boolean {
135
+ for (const [key, field] of Object.entries(this.config.credentials)) {
136
+ if (!credentials[key]) {
137
+ console.warn(`[${this.id} Provider] Missing required credential: ${key}`);
138
+ return false;
139
+ }
140
+
141
+ // Validações básicas por tipo
142
+ if (field.type === 'email' && !this.isValidEmail(credentials[key])) {
143
+ console.warn(`[${this.id} Provider] Invalid email format: ${credentials[key]}`);
144
+ return false;
145
+ }
146
+ }
147
+
148
+ return true;
149
+ }
150
+
151
+ /**
152
+ * Validação simples de email
153
+ */
154
+ private isValidEmail(email: string): boolean {
155
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
156
+ return emailRegex.test(email);
157
+ }
158
+ }