@vatts/auth 2.1.2 → 2.1.3-canary.1.0.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.
- package/dist/core.js +3 -2
- package/dist/providers/discord.d.ts +1 -1
- package/dist/providers/discord.js +26 -4
- package/dist/providers/github.d.ts +1 -1
- package/dist/providers/github.js +26 -3
- package/dist/providers/google.d.ts +1 -1
- package/dist/providers/google.js +26 -3
- package/dist/react/index.d.ts +0 -1
- package/dist/react/index.js +0 -1
- package/dist/react/react.js +72 -6
- package/dist/routes.js +91 -2
- package/dist/types.d.ts +2 -0
- package/dist/vue/session.js +69 -3
- package/package.json +2 -2
- package/dist/client.d.ts +0 -24
- package/dist/client.js +0 -146
package/dist/core.js
CHANGED
|
@@ -103,7 +103,8 @@ class VattsAuth {
|
|
|
103
103
|
path: '/',
|
|
104
104
|
httpOnly: true,
|
|
105
105
|
secure: this.config.secureCookies || false,
|
|
106
|
-
sameSite: 'strict'
|
|
106
|
+
sameSite: 'strict',
|
|
107
|
+
domain: this.config.domain || undefined
|
|
107
108
|
});
|
|
108
109
|
}
|
|
109
110
|
/**
|
|
@@ -163,7 +164,7 @@ class VattsAuth {
|
|
|
163
164
|
sameSite: 'strict', // Prevent CSRF attacks
|
|
164
165
|
maxAge: (this.config.session?.maxAge || 86400) * 1000,
|
|
165
166
|
path: '/',
|
|
166
|
-
domain: undefined // Let browser set automatically for security
|
|
167
|
+
domain: this.config.domain || undefined // Let browser set automatically for security
|
|
167
168
|
})
|
|
168
169
|
// SECURITY: Comprehensive security headers
|
|
169
170
|
.header('X-Content-Type-Options', 'nosniff')
|
|
@@ -55,7 +55,7 @@ export declare class DiscordProvider implements AuthProviderClass {
|
|
|
55
55
|
/**
|
|
56
56
|
* Gera URL de autorização do Discord
|
|
57
57
|
*/
|
|
58
|
-
getAuthorizationUrl(): string;
|
|
58
|
+
getAuthorizationUrl(isPopup?: boolean): string;
|
|
59
59
|
/**
|
|
60
60
|
* Retorna configuração pública do provider
|
|
61
61
|
*/
|
|
@@ -38,7 +38,13 @@ class DiscordProvider {
|
|
|
38
38
|
handler: async (req, params) => {
|
|
39
39
|
const url = new URL(req.url || '', 'http://localhost');
|
|
40
40
|
const code = url.searchParams.get('code');
|
|
41
|
+
const state = url.searchParams.get('state');
|
|
42
|
+
// Detecta se é modo popup pela state
|
|
43
|
+
const isPopup = state?.includes('popup=true');
|
|
41
44
|
if (!code) {
|
|
45
|
+
if (isPopup) {
|
|
46
|
+
return vatts_1.VattsResponse.redirect(`/api/auth/popup-callback?success=false&error=Authorization+code+not+provided&provider=discord`);
|
|
47
|
+
}
|
|
42
48
|
return vatts_1.VattsResponse.json({ error: 'Authorization code not provided' }, { status: 400 });
|
|
43
49
|
}
|
|
44
50
|
try {
|
|
@@ -56,9 +62,15 @@ class DiscordProvider {
|
|
|
56
62
|
})
|
|
57
63
|
});
|
|
58
64
|
if (authResponse.ok) {
|
|
59
|
-
// Propaga o cookie de sessão retornado pelo endpoint de signin
|
|
60
|
-
// e redireciona o usuário para a página de sucesso.
|
|
61
65
|
const setCookieHeader = authResponse.headers.get('set-cookie');
|
|
66
|
+
// Se é popup, redireciona para popup-callback
|
|
67
|
+
if (isPopup) {
|
|
68
|
+
const callbackUrl = this.config.successUrl || '/';
|
|
69
|
+
return vatts_1.VattsResponse
|
|
70
|
+
.redirect(`/api/auth/popup-callback?success=true&provider=discord&callbackUrl=${encodeURIComponent(callbackUrl)}`)
|
|
71
|
+
.header('Set-Cookie', setCookieHeader || '');
|
|
72
|
+
}
|
|
73
|
+
// Comportamento normal
|
|
62
74
|
if (this.config.successUrl) {
|
|
63
75
|
return vatts_1.VattsResponse
|
|
64
76
|
.redirect(this.config.successUrl)
|
|
@@ -70,11 +82,17 @@ class DiscordProvider {
|
|
|
70
82
|
else {
|
|
71
83
|
const errorText = await authResponse.text();
|
|
72
84
|
console.error(`[${this.id} Provider] Session creation failed during callback. Status: ${authResponse.status}, Body: ${errorText}`);
|
|
85
|
+
if (isPopup) {
|
|
86
|
+
return vatts_1.VattsResponse.redirect(`/api/auth/popup-callback?success=false&error=Session+creation+failed&provider=discord`);
|
|
87
|
+
}
|
|
73
88
|
return vatts_1.VattsResponse.json({ error: 'Session creation failed' }, { status: 500 });
|
|
74
89
|
}
|
|
75
90
|
}
|
|
76
91
|
catch (error) {
|
|
77
92
|
console.error(`[${this.id} Provider] Callback handler fetch error:`, error);
|
|
93
|
+
if (isPopup) {
|
|
94
|
+
return vatts_1.VattsResponse.redirect(`/api/auth/popup-callback?success=false&error=Internal+server+error&provider=discord`);
|
|
95
|
+
}
|
|
78
96
|
return vatts_1.VattsResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
79
97
|
}
|
|
80
98
|
}
|
|
@@ -88,7 +106,7 @@ class DiscordProvider {
|
|
|
88
106
|
* Método para gerar URL OAuth (usado pelo handleSignIn)
|
|
89
107
|
*/
|
|
90
108
|
handleOauth(credentials = {}) {
|
|
91
|
-
return this.getAuthorizationUrl();
|
|
109
|
+
return this.getAuthorizationUrl(credentials.popup === 'true');
|
|
92
110
|
}
|
|
93
111
|
/**
|
|
94
112
|
* Método principal - agora redireciona para OAuth ou processa callback
|
|
@@ -164,13 +182,17 @@ class DiscordProvider {
|
|
|
164
182
|
/**
|
|
165
183
|
* Gera URL de autorização do Discord
|
|
166
184
|
*/
|
|
167
|
-
getAuthorizationUrl() {
|
|
185
|
+
getAuthorizationUrl(isPopup = false) {
|
|
168
186
|
const params = new URLSearchParams({
|
|
169
187
|
client_id: this.config.clientId,
|
|
170
188
|
redirect_uri: this.config.callbackUrl || '',
|
|
171
189
|
response_type: 'code',
|
|
172
190
|
scope: (this.config.scope || this.defaultScope).join(' ')
|
|
173
191
|
});
|
|
192
|
+
// Adiciona state para indicar modo popup
|
|
193
|
+
if (isPopup) {
|
|
194
|
+
params.set('state', `popup=true×tamp=${Date.now()}`);
|
|
195
|
+
}
|
|
174
196
|
return `https://discord.com/api/oauth2/authorize?${params.toString()}`;
|
|
175
197
|
}
|
|
176
198
|
/**
|
|
@@ -55,7 +55,7 @@ export declare class GithubProvider implements AuthProviderClass {
|
|
|
55
55
|
/**
|
|
56
56
|
* Gera a URL de autorização do GitHub
|
|
57
57
|
*/
|
|
58
|
-
getAuthorizationUrl(): string;
|
|
58
|
+
getAuthorizationUrl(isPopup?: boolean): string;
|
|
59
59
|
/**
|
|
60
60
|
* Retorna a configuração pública do provider
|
|
61
61
|
*/
|
package/dist/providers/github.js
CHANGED
|
@@ -41,7 +41,13 @@ class GithubProvider {
|
|
|
41
41
|
handler: async (req, params) => {
|
|
42
42
|
const url = new URL(req.url || '', 'http://localhost');
|
|
43
43
|
const code = url.searchParams.get('code');
|
|
44
|
+
const state = url.searchParams.get('state');
|
|
45
|
+
// Detecta se é modo popup pela state
|
|
46
|
+
const isPopup = state?.includes('popup=true');
|
|
44
47
|
if (!code) {
|
|
48
|
+
if (isPopup) {
|
|
49
|
+
return vatts_1.VattsResponse.redirect(`/api/auth/popup-callback?success=false&error=Authorization+code+not+provided&provider=github`);
|
|
50
|
+
}
|
|
45
51
|
return vatts_1.VattsResponse.json({ error: 'Authorization code not provided' }, { status: 400 });
|
|
46
52
|
}
|
|
47
53
|
try {
|
|
@@ -57,8 +63,15 @@ class GithubProvider {
|
|
|
57
63
|
})
|
|
58
64
|
});
|
|
59
65
|
if (authResponse.ok) {
|
|
60
|
-
// Propaga o cookie de sessão e redireciona para a URL de sucesso
|
|
61
66
|
const setCookieHeader = authResponse.headers.get('set-cookie');
|
|
67
|
+
// Se é popup, redireciona para popup-callback
|
|
68
|
+
if (isPopup) {
|
|
69
|
+
const callbackUrl = this.config.successUrl || '/';
|
|
70
|
+
return vatts_1.VattsResponse
|
|
71
|
+
.redirect(`/api/auth/popup-callback?success=true&provider=github&callbackUrl=${encodeURIComponent(callbackUrl)}`)
|
|
72
|
+
.header('Set-Cookie', setCookieHeader || '');
|
|
73
|
+
}
|
|
74
|
+
// Comportamento normal
|
|
62
75
|
if (this.config.successUrl) {
|
|
63
76
|
return vatts_1.VattsResponse
|
|
64
77
|
.redirect(this.config.successUrl)
|
|
@@ -70,11 +83,17 @@ class GithubProvider {
|
|
|
70
83
|
else {
|
|
71
84
|
const errorText = await authResponse.text();
|
|
72
85
|
console.error(`[${this.id} Provider] Session creation failed during callback. Status: ${authResponse.status}, Body: ${errorText}`);
|
|
86
|
+
if (isPopup) {
|
|
87
|
+
return vatts_1.VattsResponse.redirect(`/api/auth/popup-callback?success=false&error=Session+creation+failed&provider=github`);
|
|
88
|
+
}
|
|
73
89
|
return vatts_1.VattsResponse.json({ error: 'Session creation failed' }, { status: 500 });
|
|
74
90
|
}
|
|
75
91
|
}
|
|
76
92
|
catch (error) {
|
|
77
93
|
console.error(`[${this.id} Provider] Callback handler fetch error:`, error);
|
|
94
|
+
if (isPopup) {
|
|
95
|
+
return vatts_1.VattsResponse.redirect(`/api/auth/popup-callback?success=false&error=Internal+server+error&provider=github`);
|
|
96
|
+
}
|
|
78
97
|
return vatts_1.VattsResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
79
98
|
}
|
|
80
99
|
}
|
|
@@ -88,7 +107,7 @@ class GithubProvider {
|
|
|
88
107
|
* Método para gerar URL OAuth (usado pelo handleSignIn)
|
|
89
108
|
*/
|
|
90
109
|
handleOauth(credentials = {}) {
|
|
91
|
-
return this.getAuthorizationUrl();
|
|
110
|
+
return this.getAuthorizationUrl(credentials.popup === 'true');
|
|
92
111
|
}
|
|
93
112
|
/**
|
|
94
113
|
* Método principal - redireciona para OAuth ou processa o callback
|
|
@@ -191,7 +210,7 @@ class GithubProvider {
|
|
|
191
210
|
/**
|
|
192
211
|
* Gera a URL de autorização do GitHub
|
|
193
212
|
*/
|
|
194
|
-
getAuthorizationUrl() {
|
|
213
|
+
getAuthorizationUrl(isPopup = false) {
|
|
195
214
|
const params = new URLSearchParams({
|
|
196
215
|
client_id: this.config.clientId,
|
|
197
216
|
redirect_uri: this.config.callbackUrl || '',
|
|
@@ -199,6 +218,10 @@ class GithubProvider {
|
|
|
199
218
|
// GitHub não usa 'response_type=code' explicitamente na URL padrão, mas aceita.
|
|
200
219
|
// O padrão é web application flow.
|
|
201
220
|
});
|
|
221
|
+
// Adiciona state para indicar modo popup
|
|
222
|
+
if (isPopup) {
|
|
223
|
+
params.set('state', `popup=true×tamp=${Date.now()}`);
|
|
224
|
+
}
|
|
202
225
|
return `https://github.com/login/oauth/authorize?${params.toString()}`;
|
|
203
226
|
}
|
|
204
227
|
/**
|
|
@@ -55,7 +55,7 @@ export declare class GoogleProvider implements AuthProviderClass {
|
|
|
55
55
|
/**
|
|
56
56
|
* Gera a URL de autorização do Google
|
|
57
57
|
*/
|
|
58
|
-
getAuthorizationUrl(): string;
|
|
58
|
+
getAuthorizationUrl(isPopup?: boolean): string;
|
|
59
59
|
/**
|
|
60
60
|
* Retorna a configuração pública do provider
|
|
61
61
|
*/
|
package/dist/providers/google.js
CHANGED
|
@@ -42,7 +42,13 @@ class GoogleProvider {
|
|
|
42
42
|
handler: async (req, params) => {
|
|
43
43
|
const url = new URL(req.url || '', 'http://localhost');
|
|
44
44
|
const code = url.searchParams.get('code');
|
|
45
|
+
const state = url.searchParams.get('state');
|
|
46
|
+
// Detecta se é modo popup pela state
|
|
47
|
+
const isPopup = state?.includes('popup=true');
|
|
45
48
|
if (!code) {
|
|
49
|
+
if (isPopup) {
|
|
50
|
+
return vatts_1.VattsResponse.redirect(`/api/auth/popup-callback?success=false&error=Authorization+code+not+provided&provider=google`);
|
|
51
|
+
}
|
|
46
52
|
return vatts_1.VattsResponse.json({ error: 'Authorization code not provided' }, { status: 400 });
|
|
47
53
|
}
|
|
48
54
|
try {
|
|
@@ -58,8 +64,15 @@ class GoogleProvider {
|
|
|
58
64
|
})
|
|
59
65
|
});
|
|
60
66
|
if (authResponse.ok) {
|
|
61
|
-
// Propaga o cookie de sessão e redireciona para a URL de sucesso
|
|
62
67
|
const setCookieHeader = authResponse.headers.get('set-cookie');
|
|
68
|
+
// Se é popup, redireciona para popup-callback
|
|
69
|
+
if (isPopup) {
|
|
70
|
+
const callbackUrl = this.config.successUrl || '/';
|
|
71
|
+
return vatts_1.VattsResponse
|
|
72
|
+
.redirect(`/api/auth/popup-callback?success=true&provider=google&callbackUrl=${encodeURIComponent(callbackUrl)}`)
|
|
73
|
+
.header('Set-Cookie', setCookieHeader || '');
|
|
74
|
+
}
|
|
75
|
+
// Comportamento normal
|
|
63
76
|
if (this.config.successUrl) {
|
|
64
77
|
return vatts_1.VattsResponse
|
|
65
78
|
.redirect(this.config.successUrl)
|
|
@@ -71,11 +84,17 @@ class GoogleProvider {
|
|
|
71
84
|
else {
|
|
72
85
|
const errorText = await authResponse.text();
|
|
73
86
|
console.error(`[${this.id} Provider] Session creation failed during callback. Status: ${authResponse.status}, Body: ${errorText}`);
|
|
87
|
+
if (isPopup) {
|
|
88
|
+
return vatts_1.VattsResponse.redirect(`/api/auth/popup-callback?success=false&error=Session+creation+failed&provider=google`);
|
|
89
|
+
}
|
|
74
90
|
return vatts_1.VattsResponse.json({ error: 'Session creation failed' }, { status: 500 });
|
|
75
91
|
}
|
|
76
92
|
}
|
|
77
93
|
catch (error) {
|
|
78
94
|
console.error(`[${this.id} Provider] Callback handler fetch error:`, error);
|
|
95
|
+
if (isPopup) {
|
|
96
|
+
return vatts_1.VattsResponse.redirect(`/api/auth/popup-callback?success=false&error=Internal+server+error&provider=google`);
|
|
97
|
+
}
|
|
79
98
|
return vatts_1.VattsResponse.json({ error: 'Internal server error' }, { status: 500 });
|
|
80
99
|
}
|
|
81
100
|
}
|
|
@@ -89,7 +108,7 @@ class GoogleProvider {
|
|
|
89
108
|
* Método para gerar URL OAuth (usado pelo handleSignIn)
|
|
90
109
|
*/
|
|
91
110
|
handleOauth(credentials = {}) {
|
|
92
|
-
return this.getAuthorizationUrl();
|
|
111
|
+
return this.getAuthorizationUrl(credentials.popup === 'true');
|
|
93
112
|
}
|
|
94
113
|
/**
|
|
95
114
|
* Método principal - redireciona para OAuth ou processa o callback
|
|
@@ -160,13 +179,17 @@ class GoogleProvider {
|
|
|
160
179
|
/**
|
|
161
180
|
* Gera a URL de autorização do Google
|
|
162
181
|
*/
|
|
163
|
-
getAuthorizationUrl() {
|
|
182
|
+
getAuthorizationUrl(isPopup = false) {
|
|
164
183
|
const params = new URLSearchParams({
|
|
165
184
|
client_id: this.config.clientId,
|
|
166
185
|
redirect_uri: this.config.callbackUrl || '',
|
|
167
186
|
response_type: 'code',
|
|
168
187
|
scope: (this.config.scope || this.defaultScope).join(' ')
|
|
169
188
|
});
|
|
189
|
+
// Adiciona state para indicar modo popup
|
|
190
|
+
if (isPopup) {
|
|
191
|
+
params.set('state', `popup=true×tamp=${Date.now()}`);
|
|
192
|
+
}
|
|
170
193
|
return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
|
|
171
194
|
}
|
|
172
195
|
/**
|
package/dist/react/index.d.ts
CHANGED
package/dist/react/index.js
CHANGED
|
@@ -33,7 +33,6 @@ exports.GuestOnly = exports.AuthGuard = exports.SessionProvider = exports.useAut
|
|
|
33
33
|
*/
|
|
34
34
|
// Exportações do frontend
|
|
35
35
|
__exportStar(require("./react"), exports);
|
|
36
|
-
__exportStar(require("../client"), exports);
|
|
37
36
|
__exportStar(require("./components"), exports);
|
|
38
37
|
// Re-exports das funções mais usadas para conveniência
|
|
39
38
|
var react_1 = require("./react");
|
package/dist/react/react.js
CHANGED
|
@@ -23,6 +23,67 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
23
23
|
const react_1 = require("react");
|
|
24
24
|
const react_2 = require("vatts/react");
|
|
25
25
|
const SessionContext = (0, react_1.createContext)(undefined);
|
|
26
|
+
/**
|
|
27
|
+
* Abre OAuth em popup e aguarda o resultado
|
|
28
|
+
*/
|
|
29
|
+
function openOAuthPopup(url, provider, fetchSession, redirect) {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const width = 600;
|
|
32
|
+
const height = 700;
|
|
33
|
+
const left = window.screenX + (window.outerWidth - width) / 2;
|
|
34
|
+
const top = window.screenY + (window.outerHeight - height) / 2;
|
|
35
|
+
const popup = window.open(url, `oauth-${provider}`, `width=${width},height=${height},left=${left},top=${top},toolbar=no,location=no,status=no,menubar=no,scrollbars=yes`);
|
|
36
|
+
if (!popup) {
|
|
37
|
+
resolve({
|
|
38
|
+
error: 'Popup blocked',
|
|
39
|
+
status: 400,
|
|
40
|
+
ok: false
|
|
41
|
+
});
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Verifica se o popup foi fechado manualmente
|
|
45
|
+
const checkPopupClosed = setInterval(() => {
|
|
46
|
+
if (popup.closed) {
|
|
47
|
+
clearInterval(checkPopupClosed);
|
|
48
|
+
window.removeEventListener('message', handleMessage);
|
|
49
|
+
resolve({
|
|
50
|
+
error: 'Popup closed',
|
|
51
|
+
status: 400,
|
|
52
|
+
ok: false
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}, 500);
|
|
56
|
+
// Listener para mensagens do popup
|
|
57
|
+
const handleMessage = async (event) => {
|
|
58
|
+
if (event.data?.type === 'oauth-success' && event.data?.provider === provider) {
|
|
59
|
+
clearInterval(checkPopupClosed);
|
|
60
|
+
window.removeEventListener('message', handleMessage);
|
|
61
|
+
popup.close();
|
|
62
|
+
// Atualiza a sessão
|
|
63
|
+
await fetchSession();
|
|
64
|
+
if (redirect && typeof window !== 'undefined') {
|
|
65
|
+
window.location.href = event.data.callbackUrl || '/';
|
|
66
|
+
}
|
|
67
|
+
resolve({
|
|
68
|
+
ok: true,
|
|
69
|
+
status: 200,
|
|
70
|
+
url: event.data.callbackUrl || '/'
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
else if (event.data?.type === 'oauth-error' && event.data?.provider === provider) {
|
|
74
|
+
clearInterval(checkPopupClosed);
|
|
75
|
+
window.removeEventListener('message', handleMessage);
|
|
76
|
+
popup.close();
|
|
77
|
+
resolve({
|
|
78
|
+
error: event.data.error || 'Authentication failed',
|
|
79
|
+
status: 401,
|
|
80
|
+
ok: false
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
window.addEventListener('message', handleMessage);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
26
87
|
function SessionProvider({ children, basePath = '/api/auth', refetchInterval = 0, refetchOnWindowFocus = true }) {
|
|
27
88
|
const [session, setSession] = (0, react_1.useState)(null);
|
|
28
89
|
const [status, setStatus] = (0, react_1.useState)('loading');
|
|
@@ -59,7 +120,7 @@ function SessionProvider({ children, basePath = '/api/auth', refetchInterval = 0
|
|
|
59
120
|
// SignIn function
|
|
60
121
|
const signIn = (0, react_1.useCallback)(async (provider = 'credentials', options = {}) => {
|
|
61
122
|
try {
|
|
62
|
-
const { redirect = true, callbackUrl, ...credentials } = options;
|
|
123
|
+
const { redirect = true, callbackUrl, popup = false, ...credentials } = options;
|
|
63
124
|
const response = await fetch(`${basePath}/signin`, {
|
|
64
125
|
method: 'POST',
|
|
65
126
|
headers: {
|
|
@@ -68,15 +129,19 @@ function SessionProvider({ children, basePath = '/api/auth', refetchInterval = 0
|
|
|
68
129
|
credentials: 'include',
|
|
69
130
|
body: JSON.stringify({
|
|
70
131
|
provider,
|
|
71
|
-
...credentials
|
|
132
|
+
...credentials,
|
|
133
|
+
popup
|
|
72
134
|
})
|
|
73
135
|
});
|
|
74
136
|
const data = await response.json();
|
|
75
137
|
if (response.ok && data.success) {
|
|
76
|
-
|
|
77
|
-
// Se é OAuth, redireciona para URL fornecida
|
|
138
|
+
// Se é OAuth, redireciona para URL fornecida ou abre popup
|
|
78
139
|
if (data.type === 'oauth' && data.redirectUrl) {
|
|
79
|
-
if (
|
|
140
|
+
if (popup && typeof window !== 'undefined') {
|
|
141
|
+
// Abre em popup
|
|
142
|
+
return await openOAuthPopup(data.redirectUrl, provider, fetchSession, redirect);
|
|
143
|
+
}
|
|
144
|
+
else if (redirect && typeof window !== 'undefined') {
|
|
80
145
|
window.location.href = data.redirectUrl;
|
|
81
146
|
}
|
|
82
147
|
return {
|
|
@@ -85,7 +150,8 @@ function SessionProvider({ children, basePath = '/api/auth', refetchInterval = 0
|
|
|
85
150
|
url: data.redirectUrl
|
|
86
151
|
};
|
|
87
152
|
}
|
|
88
|
-
// Se é sessão (credentials),
|
|
153
|
+
// Se é sessão (credentials), atualiza e redireciona
|
|
154
|
+
await fetchSession();
|
|
89
155
|
if (data.type === 'session') {
|
|
90
156
|
if (redirect && typeof window !== 'undefined') {
|
|
91
157
|
window.location.href = callbackUrl || '/';
|
package/dist/routes.js
CHANGED
|
@@ -54,6 +54,8 @@ function createAuthRoutes(config) {
|
|
|
54
54
|
return await handleCsrf(req);
|
|
55
55
|
case 'providers':
|
|
56
56
|
return await handleProviders(auth);
|
|
57
|
+
case 'popup-callback':
|
|
58
|
+
return handlePopupCallback(req);
|
|
57
59
|
default:
|
|
58
60
|
return vatts_1.VattsResponse.json({ error: 'Route not found' }, { status: 404 });
|
|
59
61
|
}
|
|
@@ -120,8 +122,12 @@ async function handleProviders(auth) {
|
|
|
120
122
|
*/
|
|
121
123
|
async function handleSignIn(req, auth) {
|
|
122
124
|
try {
|
|
123
|
-
const { provider = 'credentials', ...credentials } = await req.json();
|
|
124
|
-
|
|
125
|
+
const { provider = 'credentials', popup, ...credentials } = await req.json();
|
|
126
|
+
// Se popup está definido, passa para os credentials
|
|
127
|
+
const credentialsWithPopup = popup !== undefined
|
|
128
|
+
? { ...credentials, popup: String(popup) }
|
|
129
|
+
: credentials;
|
|
130
|
+
const result = await auth.signIn(provider, credentialsWithPopup);
|
|
125
131
|
if (!result) {
|
|
126
132
|
return vatts_1.VattsResponse.json({ error: 'Invalid credentials' }, { status: 401 });
|
|
127
133
|
}
|
|
@@ -145,6 +151,89 @@ async function handleSignIn(req, auth) {
|
|
|
145
151
|
return vatts_1.VattsResponse.json({ error: 'Authentication failed' }, { status: 500 });
|
|
146
152
|
}
|
|
147
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* Handler para GET /api/auth/popup-callback
|
|
156
|
+
* Retorna uma página HTML que envia mensagem para a janela pai e fecha o popup
|
|
157
|
+
*/
|
|
158
|
+
function handlePopupCallback(req) {
|
|
159
|
+
const url = new URL(req.url, 'http://localhost');
|
|
160
|
+
const success = url.searchParams.get('success') === 'true';
|
|
161
|
+
const error = url.searchParams.get('error');
|
|
162
|
+
const provider = url.searchParams.get('provider') || 'unknown';
|
|
163
|
+
const callbackUrl = url.searchParams.get('callbackUrl') || '/';
|
|
164
|
+
const html = `
|
|
165
|
+
<!DOCTYPE html>
|
|
166
|
+
<html>
|
|
167
|
+
<head>
|
|
168
|
+
<meta charset="UTF-8">
|
|
169
|
+
<title>Authenticating...</title>
|
|
170
|
+
<style>
|
|
171
|
+
body {
|
|
172
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
173
|
+
display: flex;
|
|
174
|
+
justify-content: center;
|
|
175
|
+
align-items: center;
|
|
176
|
+
height: 100vh;
|
|
177
|
+
margin: 0;
|
|
178
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
179
|
+
color: white;
|
|
180
|
+
}
|
|
181
|
+
.container {
|
|
182
|
+
text-align: center;
|
|
183
|
+
}
|
|
184
|
+
.spinner {
|
|
185
|
+
border: 4px solid rgba(255,255,255,0.3);
|
|
186
|
+
border-radius: 50%;
|
|
187
|
+
border-top: 4px solid white;
|
|
188
|
+
width: 40px;
|
|
189
|
+
height: 40px;
|
|
190
|
+
animation: spin 1s linear infinite;
|
|
191
|
+
margin: 0 auto 20px;
|
|
192
|
+
}
|
|
193
|
+
@keyframes spin {
|
|
194
|
+
0% { transform: rotate(0deg); }
|
|
195
|
+
100% { transform: rotate(360deg); }
|
|
196
|
+
}
|
|
197
|
+
h2 {
|
|
198
|
+
margin: 0;
|
|
199
|
+
font-size: 24px;
|
|
200
|
+
}
|
|
201
|
+
p {
|
|
202
|
+
margin: 10px 0 0;
|
|
203
|
+
opacity: 0.9;
|
|
204
|
+
}
|
|
205
|
+
</style>
|
|
206
|
+
</head>
|
|
207
|
+
<body>
|
|
208
|
+
<div class="container">
|
|
209
|
+
<div class="spinner"></div>
|
|
210
|
+
<h2>${success ? '✓ Autenticação bem-sucedida' : '✗ Erro na autenticação'}</h2>
|
|
211
|
+
<p>${success ? 'Fechando janela...' : (error || 'Algo deu errado')}</p>
|
|
212
|
+
</div>
|
|
213
|
+
<script>
|
|
214
|
+
(function() {
|
|
215
|
+
try {
|
|
216
|
+
if (window.opener) {
|
|
217
|
+
console.log('Enviando mensagem para janela pai:')
|
|
218
|
+
window.opener.postMessage({
|
|
219
|
+
type: ${success ? "'oauth-success'" : "'oauth-error'"},
|
|
220
|
+
provider: "${provider}",
|
|
221
|
+
${success ? `callbackUrl: "${callbackUrl}"` : `error: "${error || 'Authentication failed'}"`}
|
|
222
|
+
}, window.location.origin);
|
|
223
|
+
}
|
|
224
|
+
setTimeout(() => {
|
|
225
|
+
window.close();
|
|
226
|
+
}, 1000);
|
|
227
|
+
} catch (e) {
|
|
228
|
+
console.error('Error communicating with parent window:', e);
|
|
229
|
+
}
|
|
230
|
+
})();
|
|
231
|
+
</script>
|
|
232
|
+
</body>
|
|
233
|
+
</html>
|
|
234
|
+
`;
|
|
235
|
+
return vatts_1.VattsResponse.html(html);
|
|
236
|
+
}
|
|
148
237
|
/**
|
|
149
238
|
* Handler para POST /api/auth/signout
|
|
150
239
|
*/
|
package/dist/types.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export interface Session {
|
|
|
7
7
|
export interface SignInOptions {
|
|
8
8
|
redirect?: boolean;
|
|
9
9
|
callbackUrl?: string;
|
|
10
|
+
popup?: boolean;
|
|
10
11
|
[key: string]: any;
|
|
11
12
|
}
|
|
12
13
|
export interface SignInResult {
|
|
@@ -63,6 +64,7 @@ export interface AuthConfig {
|
|
|
63
64
|
secret?: string;
|
|
64
65
|
debug?: boolean;
|
|
65
66
|
secureCookies?: boolean;
|
|
67
|
+
domain?: string;
|
|
66
68
|
}
|
|
67
69
|
export interface CredentialsConfig {
|
|
68
70
|
id?: string;
|
package/dist/vue/session.js
CHANGED
|
@@ -24,6 +24,67 @@ const vue_1 = require("vue");
|
|
|
24
24
|
const vue_2 = require("vatts/vue");
|
|
25
25
|
// Chave de injeção para o TypeScript
|
|
26
26
|
exports.SessionKey = Symbol('SessionKey');
|
|
27
|
+
/**
|
|
28
|
+
* Abre OAuth em popup e aguarda o resultado
|
|
29
|
+
*/
|
|
30
|
+
function openOAuthPopup(url, provider, fetchSession, redirect) {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const width = 600;
|
|
33
|
+
const height = 700;
|
|
34
|
+
const left = window.screenX + (window.outerWidth - width) / 2;
|
|
35
|
+
const top = window.screenY + (window.outerHeight - height) / 2;
|
|
36
|
+
const popup = window.open(url, `oauth-${provider}`, `width=${width},height=${height},left=${left},top=${top},toolbar=no,location=no,status=no,menubar=no,scrollbars=yes`);
|
|
37
|
+
if (!popup) {
|
|
38
|
+
resolve({
|
|
39
|
+
error: 'Popup blocked',
|
|
40
|
+
status: 400,
|
|
41
|
+
ok: false
|
|
42
|
+
});
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// Verifica se o popup foi fechado manualmente
|
|
46
|
+
const checkPopupClosed = setInterval(() => {
|
|
47
|
+
if (popup.closed) {
|
|
48
|
+
clearInterval(checkPopupClosed);
|
|
49
|
+
window.removeEventListener('message', handleMessage);
|
|
50
|
+
resolve({
|
|
51
|
+
error: 'Popup closed',
|
|
52
|
+
status: 400,
|
|
53
|
+
ok: false
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}, 500);
|
|
57
|
+
// Listener para mensagens do popup
|
|
58
|
+
const handleMessage = async (event) => {
|
|
59
|
+
if (event.data?.type === 'oauth-success' && event.data?.provider === provider) {
|
|
60
|
+
clearInterval(checkPopupClosed);
|
|
61
|
+
window.removeEventListener('message', handleMessage);
|
|
62
|
+
popup.close();
|
|
63
|
+
// Atualiza a sessão
|
|
64
|
+
await fetchSession();
|
|
65
|
+
if (redirect && typeof window !== 'undefined') {
|
|
66
|
+
window.location.href = event.data.callbackUrl || '/';
|
|
67
|
+
}
|
|
68
|
+
resolve({
|
|
69
|
+
ok: true,
|
|
70
|
+
status: 200,
|
|
71
|
+
url: event.data.callbackUrl || '/'
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
else if (event.data?.type === 'oauth-error' && event.data?.provider === provider) {
|
|
75
|
+
clearInterval(checkPopupClosed);
|
|
76
|
+
window.removeEventListener('message', handleMessage);
|
|
77
|
+
popup.close();
|
|
78
|
+
resolve({
|
|
79
|
+
error: event.data.error || 'Authentication failed',
|
|
80
|
+
status: 401,
|
|
81
|
+
ok: false
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
window.addEventListener('message', handleMessage);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
27
88
|
/**
|
|
28
89
|
* Composable que contém toda a lógica do SessionProvider.
|
|
29
90
|
* Deve ser chamado dentro do setup do componente.
|
|
@@ -66,7 +127,7 @@ function useSessionProviderLogic(props) {
|
|
|
66
127
|
// SignIn function
|
|
67
128
|
const signIn = async (provider = 'credentials', options = {}) => {
|
|
68
129
|
try {
|
|
69
|
-
const { redirect = true, callbackUrl, ...credentials } = options;
|
|
130
|
+
const { redirect = true, callbackUrl, popup = false, ...credentials } = options;
|
|
70
131
|
const response = await fetch(`${props.basePath}/signin`, {
|
|
71
132
|
method: 'POST',
|
|
72
133
|
headers: {
|
|
@@ -80,13 +141,18 @@ function useSessionProviderLogic(props) {
|
|
|
80
141
|
});
|
|
81
142
|
const data = await response.json();
|
|
82
143
|
if (response.ok && data.success) {
|
|
83
|
-
await fetchSession();
|
|
84
144
|
if (data.type === 'oauth' && data.redirectUrl) {
|
|
85
|
-
if (
|
|
145
|
+
if (popup && typeof window !== 'undefined') {
|
|
146
|
+
// Abre em popup
|
|
147
|
+
return await openOAuthPopup(data.redirectUrl, provider, fetchSession, redirect);
|
|
148
|
+
}
|
|
149
|
+
else if (redirect && typeof window !== 'undefined') {
|
|
86
150
|
window.location.href = data.redirectUrl;
|
|
87
151
|
}
|
|
88
152
|
return { ok: true, status: 200, url: data.redirectUrl };
|
|
89
153
|
}
|
|
154
|
+
// Se é sessão (credentials), atualiza e redireciona
|
|
155
|
+
await fetchSession();
|
|
90
156
|
if (data.type === 'session') {
|
|
91
157
|
const finalUrl = callbackUrl || '/';
|
|
92
158
|
if (redirect && typeof window !== 'undefined') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vatts/auth",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.3-canary.1.0.1",
|
|
4
4
|
"description": "Authentication package for Vatts.js framework",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"react": "^19.2.0",
|
|
51
51
|
"react-dom": "^19.2.0",
|
|
52
52
|
"vue": "^3.5.27",
|
|
53
|
-
"vatts": "^2.1.
|
|
53
|
+
"vatts": "^2.1.3-canary.1.0.1"
|
|
54
54
|
},
|
|
55
55
|
"peerDependenciesMeta": {
|
|
56
56
|
"react-dom": {
|
package/dist/client.d.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { SignInOptions, SignInResult, Session } from './types';
|
|
2
|
-
export declare function setBasePath(path: string): void;
|
|
3
|
-
/**
|
|
4
|
-
* Função para obter a sessão atual (similar ao NextAuth getSession)
|
|
5
|
-
*/
|
|
6
|
-
export declare function getSession(): Promise<Session | null>;
|
|
7
|
-
/**
|
|
8
|
-
* Função para obter token CSRF
|
|
9
|
-
*/
|
|
10
|
-
export declare function getCsrfToken(): Promise<string | null>;
|
|
11
|
-
/**
|
|
12
|
-
* Função para obter providers disponíveis
|
|
13
|
-
*/
|
|
14
|
-
export declare function getProviders(): Promise<any[] | null>;
|
|
15
|
-
/**
|
|
16
|
-
* Função para fazer login (similar ao NextAuth signIn)
|
|
17
|
-
*/
|
|
18
|
-
export declare function signIn(provider?: string, options?: SignInOptions): Promise<SignInResult | undefined>;
|
|
19
|
-
/**
|
|
20
|
-
* Função para fazer logout (similar ao NextAuth signOut)
|
|
21
|
-
*/
|
|
22
|
-
export declare function signOut(options?: {
|
|
23
|
-
callbackUrl?: string;
|
|
24
|
-
}): Promise<void>;
|
package/dist/client.js
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.setBasePath = setBasePath;
|
|
4
|
-
exports.getSession = getSession;
|
|
5
|
-
exports.getCsrfToken = getCsrfToken;
|
|
6
|
-
exports.getProviders = getProviders;
|
|
7
|
-
exports.signIn = signIn;
|
|
8
|
-
exports.signOut = signOut;
|
|
9
|
-
// Configuração global do client
|
|
10
|
-
let basePath = '/api/auth';
|
|
11
|
-
function setBasePath(path) {
|
|
12
|
-
basePath = path;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Função para obter a sessão atual (similar ao NextAuth getSession)
|
|
16
|
-
*/
|
|
17
|
-
async function getSession() {
|
|
18
|
-
try {
|
|
19
|
-
const response = await fetch(`${basePath}/session`, {
|
|
20
|
-
credentials: 'include'
|
|
21
|
-
});
|
|
22
|
-
if (!response.ok) {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
const data = await response.json();
|
|
26
|
-
return data.session || null;
|
|
27
|
-
}
|
|
28
|
-
catch (error) {
|
|
29
|
-
console.error('[vatts-auth] Error fetching session:', error);
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Função para obter token CSRF
|
|
35
|
-
*/
|
|
36
|
-
async function getCsrfToken() {
|
|
37
|
-
try {
|
|
38
|
-
const response = await fetch(`${basePath}/csrf`, {
|
|
39
|
-
credentials: 'include'
|
|
40
|
-
});
|
|
41
|
-
if (!response.ok) {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
const data = await response.json();
|
|
45
|
-
return data.csrfToken || null;
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
console.error('[vatts-auth] Error fetching CSRF token:', error);
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Função para obter providers disponíveis
|
|
54
|
-
*/
|
|
55
|
-
async function getProviders() {
|
|
56
|
-
try {
|
|
57
|
-
const response = await fetch(`${basePath}/providers`, {
|
|
58
|
-
credentials: 'include'
|
|
59
|
-
});
|
|
60
|
-
if (!response.ok) {
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
const data = await response.json();
|
|
64
|
-
return data.providers || [];
|
|
65
|
-
}
|
|
66
|
-
catch (error) {
|
|
67
|
-
console.error('[vatts-auth] Error searching for providers:', error);
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Função para fazer login (similar ao NextAuth signIn)
|
|
73
|
-
*/
|
|
74
|
-
async function signIn(provider = 'credentials', options = {}) {
|
|
75
|
-
try {
|
|
76
|
-
const { redirect = true, callbackUrl, ...credentials } = options;
|
|
77
|
-
const response = await fetch(`${basePath}/signin`, {
|
|
78
|
-
method: 'POST',
|
|
79
|
-
headers: {
|
|
80
|
-
'Content-Type': 'application/json',
|
|
81
|
-
},
|
|
82
|
-
credentials: 'include',
|
|
83
|
-
body: JSON.stringify({
|
|
84
|
-
provider,
|
|
85
|
-
...credentials
|
|
86
|
-
})
|
|
87
|
-
});
|
|
88
|
-
const data = await response.json();
|
|
89
|
-
if (response.ok && data.success) {
|
|
90
|
-
// Se é OAuth, redireciona para URL fornecida
|
|
91
|
-
if (data.type === 'oauth' && data.redirectUrl) {
|
|
92
|
-
if (redirect && typeof window !== 'undefined') {
|
|
93
|
-
window.location.href = data.redirectUrl;
|
|
94
|
-
}
|
|
95
|
-
return {
|
|
96
|
-
ok: true,
|
|
97
|
-
status: 200,
|
|
98
|
-
url: data.redirectUrl
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
// Se é sessão (credentials), redireciona para callbackUrl
|
|
102
|
-
if (data.type === 'session') {
|
|
103
|
-
if (redirect && typeof window !== 'undefined') {
|
|
104
|
-
window.location.href = callbackUrl || '/';
|
|
105
|
-
}
|
|
106
|
-
return {
|
|
107
|
-
ok: true,
|
|
108
|
-
status: 200,
|
|
109
|
-
url: callbackUrl || '/'
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
return {
|
|
115
|
-
error: data.error || 'Authentication failed',
|
|
116
|
-
status: response.status,
|
|
117
|
-
ok: false
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
catch (error) {
|
|
122
|
-
console.error('[vatts-auth] Error on signIn:', error);
|
|
123
|
-
return {
|
|
124
|
-
error: 'Network error',
|
|
125
|
-
status: 500,
|
|
126
|
-
ok: false
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Função para fazer logout (similar ao NextAuth signOut)
|
|
132
|
-
*/
|
|
133
|
-
async function signOut(options = {}) {
|
|
134
|
-
try {
|
|
135
|
-
await fetch(`${basePath}/signout`, {
|
|
136
|
-
method: 'POST',
|
|
137
|
-
credentials: 'include'
|
|
138
|
-
});
|
|
139
|
-
if (typeof window !== 'undefined') {
|
|
140
|
-
window.location.href = options.callbackUrl || '/';
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
catch (error) {
|
|
144
|
-
console.error('[vatts-auth] Error on signOut:', error);
|
|
145
|
-
}
|
|
146
|
-
}
|