@brightweblabs/core-auth 0.1.2 → 0.2.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.
- package/package.json +3 -5
- package/src/client.ts +0 -58
- package/src/server.ts +6 -26
- package/src/shared.ts +83 -0
- package/src/index.ts +0 -1
package/package.json
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brightweblabs/core-auth",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
5
|
-
"main": "./src/index.ts",
|
|
6
|
-
"types": "./src/index.ts",
|
|
4
|
+
"version": "0.2.0",
|
|
7
5
|
"files": [
|
|
8
6
|
"src"
|
|
9
7
|
],
|
|
@@ -16,9 +14,9 @@
|
|
|
16
14
|
"access": "public"
|
|
17
15
|
},
|
|
18
16
|
"exports": {
|
|
19
|
-
".": "./src/index.ts",
|
|
20
17
|
"./client": "./src/client.ts",
|
|
21
|
-
"./server": "./src/server.ts"
|
|
18
|
+
"./server": "./src/server.ts",
|
|
19
|
+
"./shared": "./src/shared.ts"
|
|
22
20
|
},
|
|
23
21
|
"dependencies": {
|
|
24
22
|
"next": "16.1.1",
|
package/src/client.ts
CHANGED
|
@@ -2,13 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
4
|
|
|
5
|
-
export const AUTH_RESEND_COOLDOWN_SECONDS = 60;
|
|
6
|
-
|
|
7
|
-
export interface PasswordValidationResult {
|
|
8
|
-
isValid: boolean;
|
|
9
|
-
errors: string[];
|
|
10
|
-
}
|
|
11
|
-
|
|
12
5
|
interface CooldownTimerResult {
|
|
13
6
|
remaining: number;
|
|
14
7
|
isCoolingDown: boolean;
|
|
@@ -16,57 +9,6 @@ interface CooldownTimerResult {
|
|
|
16
9
|
reset: () => void;
|
|
17
10
|
}
|
|
18
11
|
|
|
19
|
-
export function getAuthBaseUrl(): string {
|
|
20
|
-
const configuredUrl = process.env.NEXT_PUBLIC_APP_URL?.trim();
|
|
21
|
-
if (!configuredUrl) {
|
|
22
|
-
throw new Error("Falta NEXT_PUBLIC_APP_URL para redirecionamentos de email de autenticação.");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
return new URL(configuredUrl).toString().replace(/\/$/, "");
|
|
27
|
-
} catch {
|
|
28
|
-
throw new Error("NEXT_PUBLIC_APP_URL inválido. Esperado URL absoluto (ex.: https://exemplo.com).");
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function buildSignupCallbackUrl(): string {
|
|
33
|
-
return new URL("auth/callback", `${getAuthBaseUrl()}/`).toString();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function buildResetPasswordRedirectUrl(): string {
|
|
37
|
-
return `${getAuthBaseUrl()}/reset-password`;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function validatePassword(password: string): PasswordValidationResult {
|
|
41
|
-
const errors: string[] = [];
|
|
42
|
-
|
|
43
|
-
if (password.length < 8) {
|
|
44
|
-
errors.push("A palavra-passe deve ter pelo menos 8 caracteres");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (!/[A-Z]/.test(password)) {
|
|
48
|
-
errors.push("A palavra-passe deve conter pelo menos uma letra maiúscula");
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (!/[a-z]/.test(password)) {
|
|
52
|
-
errors.push("A palavra-passe deve conter pelo menos uma letra minúscula");
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (!/[0-9]/.test(password)) {
|
|
56
|
-
errors.push("A palavra-passe deve conter pelo menos um número");
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
isValid: errors.length === 0,
|
|
61
|
-
errors,
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function validateEmail(email: string): boolean {
|
|
66
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
67
|
-
return emailRegex.test(email.trim().toLowerCase());
|
|
68
|
-
}
|
|
69
|
-
|
|
70
12
|
export function useCooldownTimer(durationSeconds: number): CooldownTimerResult {
|
|
71
13
|
const [remaining, setRemaining] = useState(0);
|
|
72
14
|
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
package/src/server.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import type { User } from "@supabase/supabase-js";
|
|
2
2
|
import { redirect } from "next/navigation";
|
|
3
3
|
import { createServerSupabase } from "@brightweblabs/infra/server";
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
canAccessInsightsCms,
|
|
6
|
+
normalizeGlobalRole,
|
|
7
|
+
resolvePostLoginPath,
|
|
8
|
+
type GlobalRole,
|
|
9
|
+
} from "./shared";
|
|
6
10
|
|
|
7
11
|
type Profile = {
|
|
8
12
|
id: string;
|
|
@@ -44,30 +48,6 @@ type ServerUserAccess =
|
|
|
44
48
|
error: string;
|
|
45
49
|
};
|
|
46
50
|
|
|
47
|
-
const INSIGHTS_CMS_ALLOWED_ROLES: GlobalRole[] = ["admin"];
|
|
48
|
-
const DASHBOARD_LANDING_ROLES: GlobalRole[] = ["staff", "admin"];
|
|
49
|
-
|
|
50
|
-
export function normalizeGlobalRole(value: string | null | undefined): GlobalRole | null {
|
|
51
|
-
const normalizedValue = (value ?? "").trim().toLowerCase();
|
|
52
|
-
if (normalizedValue === "client" || normalizedValue === "staff" || normalizedValue === "admin") {
|
|
53
|
-
return normalizedValue;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function canAccessInsightsCms(role: string | null | undefined): boolean {
|
|
60
|
-
const normalizedRole = normalizeGlobalRole(role);
|
|
61
|
-
return normalizedRole !== null && INSIGHTS_CMS_ALLOWED_ROLES.some((allowedRole) => allowedRole === normalizedRole);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function resolvePostLoginPath(role: string | null | undefined): "/dashboard" | "/account" {
|
|
65
|
-
const normalizedRole = normalizeGlobalRole(role);
|
|
66
|
-
const shouldLandOnDashboard = normalizedRole !== null
|
|
67
|
-
&& DASHBOARD_LANDING_ROLES.some((allowedRole) => allowedRole === normalizedRole);
|
|
68
|
-
return shouldLandOnDashboard ? "/dashboard" : "/account";
|
|
69
|
-
}
|
|
70
|
-
|
|
71
51
|
export async function requireServerUserAccess(): Promise<ServerUserAccess> {
|
|
72
52
|
const supabase = await createServerSupabase();
|
|
73
53
|
const {
|
package/src/shared.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export const AUTH_RESEND_COOLDOWN_SECONDS = 60;
|
|
2
|
+
|
|
3
|
+
export interface PasswordValidationResult {
|
|
4
|
+
isValid: boolean;
|
|
5
|
+
errors: string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type GlobalRole = "client" | "staff" | "admin";
|
|
9
|
+
|
|
10
|
+
const INSIGHTS_CMS_ALLOWED_ROLES: GlobalRole[] = ["admin"];
|
|
11
|
+
const DASHBOARD_LANDING_ROLES: GlobalRole[] = ["staff", "admin"];
|
|
12
|
+
|
|
13
|
+
export function getAuthBaseUrl(): string {
|
|
14
|
+
const configuredUrl = process.env.NEXT_PUBLIC_APP_URL?.trim();
|
|
15
|
+
if (!configuredUrl) {
|
|
16
|
+
throw new Error("Falta NEXT_PUBLIC_APP_URL para redirecionamentos de email de autenticação.");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
return new URL(configuredUrl).toString().replace(/\/$/, "");
|
|
21
|
+
} catch {
|
|
22
|
+
throw new Error("NEXT_PUBLIC_APP_URL inválido. Esperado URL absoluto (ex.: https://exemplo.com).");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function buildSignupCallbackUrl(): string {
|
|
27
|
+
return new URL("auth/callback", `${getAuthBaseUrl()}/`).toString();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function buildResetPasswordRedirectUrl(): string {
|
|
31
|
+
return `${getAuthBaseUrl()}/reset-password`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function validatePassword(password: string): PasswordValidationResult {
|
|
35
|
+
const errors: string[] = [];
|
|
36
|
+
|
|
37
|
+
if (password.length < 8) {
|
|
38
|
+
errors.push("A palavra-passe deve ter pelo menos 8 caracteres");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!/[A-Z]/.test(password)) {
|
|
42
|
+
errors.push("A palavra-passe deve conter pelo menos uma letra maiúscula");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!/[a-z]/.test(password)) {
|
|
46
|
+
errors.push("A palavra-passe deve conter pelo menos uma letra minúscula");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!/[0-9]/.test(password)) {
|
|
50
|
+
errors.push("A palavra-passe deve conter pelo menos um número");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
isValid: errors.length === 0,
|
|
55
|
+
errors,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function validateEmail(email: string): boolean {
|
|
60
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
61
|
+
return emailRegex.test(email.trim().toLowerCase());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function normalizeGlobalRole(value: string | null | undefined): GlobalRole | null {
|
|
65
|
+
const normalizedValue = (value ?? "").trim().toLowerCase();
|
|
66
|
+
if (normalizedValue === "client" || normalizedValue === "staff" || normalizedValue === "admin") {
|
|
67
|
+
return normalizedValue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function canAccessInsightsCms(role: string | null | undefined): boolean {
|
|
74
|
+
const normalizedRole = normalizeGlobalRole(role);
|
|
75
|
+
return normalizedRole !== null && INSIGHTS_CMS_ALLOWED_ROLES.some((allowedRole) => allowedRole === normalizedRole);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function resolvePostLoginPath(role: string | null | undefined): "/dashboard" | "/account" {
|
|
79
|
+
const normalizedRole = normalizeGlobalRole(role);
|
|
80
|
+
const shouldLandOnDashboard = normalizedRole !== null
|
|
81
|
+
&& DASHBOARD_LANDING_ROLES.some((allowedRole) => allowedRole === normalizedRole);
|
|
82
|
+
return shouldLandOnDashboard ? "/dashboard" : "/account";
|
|
83
|
+
}
|
package/src/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./client";
|