@veap/auth 0.1.6 → 0.1.8
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/README.md +10 -4
- package/dist/actions/auth.actions.d.ts +2 -18
- package/dist/actions/auth.actions.js +16 -4
- package/dist/common/context.d.ts +2 -0
- package/dist/common/logic.js +5 -2
- package/dist/common/types.d.ts +1 -0
- package/dist/composition.d.ts +9 -0
- package/dist/composition.js +5 -0
- package/dist/crypto/encryption.js +0 -2
- package/dist/crypto/password.d.ts +4 -4
- package/dist/crypto/password.js +41 -9
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/models/User.d.ts +1 -0
- package/dist/models/User.js +3 -1
- package/dist/notifications/contracts.d.ts +29 -0
- package/dist/notifications/contracts.js +4 -0
- package/dist/notifications/default-notification.service.d.ts +30 -0
- package/dist/notifications/default-notification.service.js +105 -0
- package/dist/notifications/helpers.d.ts +16 -0
- package/dist/notifications/helpers.js +39 -0
- package/dist/notifications/index.d.ts +3 -0
- package/dist/notifications/index.js +3 -0
- package/dist/passwords/password-reset.facade.js +6 -4
- package/dist/passwords/password-reset.service.d.ts +3 -1
- package/dist/passwords/password-reset.service.js +9 -4
- package/dist/providers/auth.provider.js +19 -2
- package/dist/providers/setup.js +12 -1
- package/dist/rbac/rbac.facade.d.ts +2 -2
- package/dist/rbac/rbac.facade.js +6 -4
- package/dist/rbac/rbac.service.d.ts +2 -2
- package/dist/rbac/rbac.service.js +2 -2
- package/dist/server.d.ts +10 -1
- package/dist/server.js +10 -1
- package/dist/session/auth.service.js +3 -0
- package/dist/session/session.facade.js +6 -4
- package/dist/session/session.service.d.ts +1 -1
- package/dist/session/session.service.js +14 -3
- package/dist/users/user.facade.js +6 -4
- package/dist/users/user.service.d.ts +3 -1
- package/dist/users/user.service.js +12 -5
- package/dist/validation/index.js +1 -1
- package/dist/verification/verification.facade.js +6 -4
- package/dist/verification/verification.service.d.ts +3 -1
- package/dist/verification/verification.service.js +9 -4
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -82,9 +82,10 @@ import { AuthServiceProvider } from "@veap/auth";
|
|
|
82
82
|
export const app = Application.configure()
|
|
83
83
|
.use(DatabaseServiceProvider)
|
|
84
84
|
.use(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
85
|
+
(c) =>
|
|
86
|
+
new AuthServiceProvider(c, {
|
|
87
|
+
autoMigrate: true, // Automatically applies auth tables on boot
|
|
88
|
+
}),
|
|
88
89
|
)
|
|
89
90
|
.create();
|
|
90
91
|
```
|
|
@@ -108,6 +109,10 @@ export async function handleLogin(formData: FormData) {
|
|
|
108
109
|
return { error: result.error?.message || "Invalid credentials" };
|
|
109
110
|
}
|
|
110
111
|
|
|
112
|
+
if (result.data.status === "CHALLENGE_REQUIRED") {
|
|
113
|
+
return { challenge: result.data };
|
|
114
|
+
}
|
|
115
|
+
|
|
111
116
|
return { user: result.data.user };
|
|
112
117
|
}
|
|
113
118
|
|
|
@@ -131,7 +136,7 @@ import { hashPassword } from "@veap/auth/server";
|
|
|
131
136
|
|
|
132
137
|
// Create a new user with hashed credentials
|
|
133
138
|
const user = await User.create({
|
|
134
|
-
|
|
139
|
+
username: "janedoe",
|
|
135
140
|
email: "jane@example.com",
|
|
136
141
|
password: await hashPassword("securePassword123"),
|
|
137
142
|
recovery_code: Buffer.from("recovery-key-bytes"),
|
|
@@ -154,6 +159,7 @@ if (adminRole) {
|
|
|
154
159
|
| `@veap/auth/actions` | Next.js Server Actions directly exportable for actions boundaries |
|
|
155
160
|
| `@veap/auth/models` | Active Record models (`User`, `Session`, `Role`, `Permission`) |
|
|
156
161
|
| `@veap/auth/migrations` | Knex migrations for authentication tables |
|
|
162
|
+
| `@veap/auth/validation` | Zod validation schemas (`loginSchema`, `registerSchema`, and others) |
|
|
157
163
|
|
|
158
164
|
## Documentation
|
|
159
165
|
|
|
@@ -2,22 +2,6 @@ import { type Result } from "@veap/kernel";
|
|
|
2
2
|
import type { AuthResponse, SessionFlags } from "../common/index.js";
|
|
3
3
|
import type { LoginInput, RegisterInput } from "../validation/index.js";
|
|
4
4
|
export declare function signIn(data: LoginInput): Promise<Result<AuthResponse>>;
|
|
5
|
-
export declare function signUp(data: RegisterInput): Promise<
|
|
6
|
-
|
|
7
|
-
error: import("@veap/kernel").AppErrorJson;
|
|
8
|
-
data?: never;
|
|
9
|
-
} | {
|
|
10
|
-
success: true;
|
|
11
|
-
data: AuthResponse;
|
|
12
|
-
error?: never;
|
|
13
|
-
}>;
|
|
14
|
-
export declare function finalizeLogin(userId: string, flags: SessionFlags): Promise<{
|
|
15
|
-
success: false;
|
|
16
|
-
error: import("@veap/kernel").AppErrorJson;
|
|
17
|
-
data?: never;
|
|
18
|
-
} | {
|
|
19
|
-
success: true;
|
|
20
|
-
data: AuthResponse;
|
|
21
|
-
error?: never;
|
|
22
|
-
}>;
|
|
5
|
+
export declare function signUp(data: RegisterInput): Promise<Result<AuthResponse>>;
|
|
6
|
+
export declare function finalizeLogin(userId: string, flags: SessionFlags): Promise<Result<AuthResponse>>;
|
|
23
7
|
export declare function signOut(): Promise<Result<void>>;
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"use server";
|
|
2
|
-
import { app, handleActionError, ok } from "@veap/kernel";
|
|
2
|
+
import { AppError, app, handleActionError, ok, } from "@veap/kernel";
|
|
3
3
|
import { AuthService } from "../session/auth.service.js";
|
|
4
4
|
export async function signIn(data) {
|
|
5
5
|
try {
|
|
6
6
|
const authService = await app(AuthService);
|
|
7
|
-
|
|
7
|
+
const response = await authService.signIn(data);
|
|
8
|
+
if (response.status === "ERROR") {
|
|
9
|
+
throw AppError.Unauthorized(response.message || "Invalid credentials");
|
|
10
|
+
}
|
|
11
|
+
return ok(response);
|
|
8
12
|
}
|
|
9
13
|
catch (e) {
|
|
10
14
|
return handleActionError(e, "Auth:SignIn");
|
|
@@ -13,7 +17,11 @@ export async function signIn(data) {
|
|
|
13
17
|
export async function signUp(data) {
|
|
14
18
|
try {
|
|
15
19
|
const authService = await app(AuthService);
|
|
16
|
-
|
|
20
|
+
const response = await authService.signUp(data);
|
|
21
|
+
if (response.status === "ERROR") {
|
|
22
|
+
throw AppError.Conflict(response.message || "Registration failed");
|
|
23
|
+
}
|
|
24
|
+
return ok(response);
|
|
17
25
|
}
|
|
18
26
|
catch (e) {
|
|
19
27
|
return handleActionError(e, "Auth:SignUp");
|
|
@@ -22,7 +30,11 @@ export async function signUp(data) {
|
|
|
22
30
|
export async function finalizeLogin(userId, flags) {
|
|
23
31
|
try {
|
|
24
32
|
const authService = await app(AuthService);
|
|
25
|
-
|
|
33
|
+
const response = await authService.finalizeLogin(userId, flags);
|
|
34
|
+
if (response.status === "ERROR") {
|
|
35
|
+
throw AppError.BadRequest(response.message || "Failed to finalize login");
|
|
36
|
+
}
|
|
37
|
+
return ok(response);
|
|
26
38
|
}
|
|
27
39
|
catch (e) {
|
|
28
40
|
return handleActionError(e, "Auth:FinalizeLogin");
|
package/dist/common/context.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { IPasswordHasher } from "../crypto/contracts.js";
|
|
1
2
|
import { type AuthRoutesConfig } from "./routes.js";
|
|
2
3
|
export interface AuthContext {
|
|
3
4
|
user: any;
|
|
@@ -6,6 +7,7 @@ export interface AuthContext {
|
|
|
6
7
|
passwordReset: any;
|
|
7
8
|
emailVerification: any;
|
|
8
9
|
auth: any;
|
|
10
|
+
passwordHasher?: IPasswordHasher;
|
|
9
11
|
routes?: AuthRoutesConfig;
|
|
10
12
|
}
|
|
11
13
|
/**
|
package/dist/common/logic.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { augmentSession, augmentUser, registerIdentityAugmenter, registerPasswordResetSessionAugmenter, registerSessionAugmenter, unregisterIdentityAugmenter, unregisterPasswordResetSessionAugmenter, unregisterSessionAugmenter, } from "./augment.js";
|
|
2
|
-
import { authContext, isAuthContextBound } from "./context.js";
|
|
2
|
+
import { authContext, getAuthRoutes, isAuthContextBound } from "./context.js";
|
|
3
3
|
import { AuthCallbackRegistry } from "./registry.js";
|
|
4
4
|
const globalForAuth = globalThis;
|
|
5
5
|
export const authValidators = globalForAuth.__VEAP_AUTH_VALIDATORS__ ??
|
|
@@ -64,7 +64,10 @@ export async function checkSecurity(session, user, requiredRoles, requiredPermis
|
|
|
64
64
|
path) {
|
|
65
65
|
if (!user) {
|
|
66
66
|
console.warn("User is required for security check");
|
|
67
|
-
return {
|
|
67
|
+
return {
|
|
68
|
+
satisfied: false,
|
|
69
|
+
redirect: fallbackRedirect ?? getAuthRoutes().signIn,
|
|
70
|
+
};
|
|
68
71
|
}
|
|
69
72
|
const userRoles = Array.isArray(user.roles) ? user.roles : [];
|
|
70
73
|
const userPermissions = Array.isArray(user.permissions)
|
package/dist/common/types.d.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hashes the password using
|
|
2
|
+
* Hashes the password using the configured password hasher.
|
|
3
3
|
* @param password Password to be hashed.
|
|
4
4
|
* @returns Returns the hashed password.
|
|
5
5
|
*/
|
|
6
6
|
export declare function hashPassword(password: string): Promise<string>;
|
|
7
7
|
/**
|
|
8
|
-
* Verifies the password hash.
|
|
9
|
-
* @param hash
|
|
8
|
+
* Verifies the password hash using the configured password hasher.
|
|
9
|
+
* @param hash Stored password hash.
|
|
10
10
|
* @param password Password for comparison.
|
|
11
11
|
* @returns Returns true if the password is correct, false otherwise.
|
|
12
12
|
*/
|
|
13
13
|
export declare function verifyPasswordHash(hash: string, password: string): Promise<boolean>;
|
|
14
14
|
/**
|
|
15
|
-
* Validates password strength.
|
|
15
|
+
* Validates password strength using the configured password hasher.
|
|
16
16
|
* @param password Password to validate.
|
|
17
17
|
* @returns Returns true if the password meets complexity requirements.
|
|
18
18
|
*/
|
package/dist/crypto/password.js
CHANGED
|
@@ -1,27 +1,59 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { app } from "@veap/kernel";
|
|
2
|
+
import { authContext, isAuthContextBound } from "../common/context.js";
|
|
3
|
+
import { BcryptPasswordHasher } from "./bcrypt-hasher.js";
|
|
4
|
+
import { PASSWORD_HASHER } from "./contracts.js";
|
|
5
|
+
async function resolvePasswordHasher() {
|
|
6
|
+
if (isAuthContextBound()) {
|
|
7
|
+
const ctx = authContext();
|
|
8
|
+
if (ctx.passwordHasher) {
|
|
9
|
+
return ctx.passwordHasher;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const g = globalThis;
|
|
13
|
+
if (g.__VEAP_BOOTSTRAPPING_PROMISE__) {
|
|
14
|
+
try {
|
|
15
|
+
await g.__VEAP_BOOTSTRAPPING_PROMISE__;
|
|
16
|
+
}
|
|
17
|
+
catch { }
|
|
18
|
+
if (isAuthContextBound()) {
|
|
19
|
+
const ctx = authContext();
|
|
20
|
+
if (ctx.passwordHasher) {
|
|
21
|
+
return ctx.passwordHasher;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
return await app(PASSWORD_HASHER);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return new BcryptPasswordHasher();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
2
32
|
/**
|
|
3
|
-
* Hashes the password using
|
|
33
|
+
* Hashes the password using the configured password hasher.
|
|
4
34
|
* @param password Password to be hashed.
|
|
5
35
|
* @returns Returns the hashed password.
|
|
6
36
|
*/
|
|
7
37
|
export async function hashPassword(password) {
|
|
8
|
-
const
|
|
9
|
-
return await
|
|
38
|
+
const hasher = await resolvePasswordHasher();
|
|
39
|
+
return await hasher.hash(password);
|
|
10
40
|
}
|
|
11
41
|
/**
|
|
12
|
-
* Verifies the password hash.
|
|
13
|
-
* @param hash
|
|
42
|
+
* Verifies the password hash using the configured password hasher.
|
|
43
|
+
* @param hash Stored password hash.
|
|
14
44
|
* @param password Password for comparison.
|
|
15
45
|
* @returns Returns true if the password is correct, false otherwise.
|
|
16
46
|
*/
|
|
17
47
|
export async function verifyPasswordHash(hash, password) {
|
|
18
|
-
|
|
48
|
+
const hasher = await resolvePasswordHasher();
|
|
49
|
+
return await hasher.verify(hash, password);
|
|
19
50
|
}
|
|
20
51
|
/**
|
|
21
|
-
* Validates password strength.
|
|
52
|
+
* Validates password strength using the configured password hasher.
|
|
22
53
|
* @param password Password to validate.
|
|
23
54
|
* @returns Returns true if the password meets complexity requirements.
|
|
24
55
|
*/
|
|
25
56
|
export async function verifyPasswordStrength(password) {
|
|
26
|
-
|
|
57
|
+
const hasher = await resolvePasswordHasher();
|
|
58
|
+
return await hasher.validateStrength(password);
|
|
27
59
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export * from "./actions/index.js";
|
|
2
|
-
export { AuthProvider, useAuthRoutes, useUser } from "./client/index.js";
|
|
3
2
|
export * from "./common/index.js";
|
|
3
|
+
export * from "./composition.js";
|
|
4
4
|
export * from "./crypto/index.js";
|
|
5
|
+
export * from "./notifications/index.js";
|
|
5
6
|
export * from "./passwords/index.js";
|
|
6
7
|
export * from "./providers/index.js";
|
|
7
8
|
export * from "./rbac/index.js";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export * from "./actions/index.js";
|
|
2
|
-
export { AuthProvider, useAuthRoutes, useUser } from "./client/index.js";
|
|
3
2
|
export * from "./common/index.js";
|
|
3
|
+
export * from "./composition.js";
|
|
4
4
|
export * from "./crypto/index.js";
|
|
5
|
+
export * from "./notifications/index.js";
|
|
5
6
|
export * from "./passwords/index.js";
|
|
6
7
|
export * from "./providers/index.js";
|
|
7
8
|
export * from "./rbac/index.js";
|
package/dist/models/User.d.ts
CHANGED
package/dist/models/User.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Model } from "@veap/database";
|
|
1
|
+
import { Model, MorphMap } from "@veap/database";
|
|
2
2
|
import { Permission } from "./Permission.js";
|
|
3
3
|
import { Role } from "./Role.js";
|
|
4
4
|
import { Session } from "./Session.js";
|
|
5
5
|
export class User extends Model {
|
|
6
6
|
static table = "users";
|
|
7
|
+
static morphAlias = "user";
|
|
7
8
|
static hidden = ["password", "recovery_code"];
|
|
8
9
|
static casts = {
|
|
9
10
|
emailVerifiedAt: "datetime",
|
|
@@ -29,3 +30,4 @@ export class User extends Model {
|
|
|
29
30
|
return this.belongsToMany(Permission, "users_to_permissions", "user_id", "permission_id", "id", "id");
|
|
30
31
|
}
|
|
31
32
|
}
|
|
33
|
+
MorphMap.register({ user: User });
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Token } from "@veap/kernel";
|
|
2
|
+
/**
|
|
3
|
+
* Port contract for authentication notifications (emails, SMS, security alerts).
|
|
4
|
+
*
|
|
5
|
+
* Decouples authentication use cases (password reset, email verification,
|
|
6
|
+
* recovery codes) from specific transport providers (SMTP, SendGrid, SES, etc.).
|
|
7
|
+
*/
|
|
8
|
+
export interface IAuthNotificationService {
|
|
9
|
+
/**
|
|
10
|
+
* Sends an email verification request containing an OTP code.
|
|
11
|
+
*/
|
|
12
|
+
sendVerificationEmail(email: string, code: string): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Sends a password reset request containing an OTP code.
|
|
15
|
+
*/
|
|
16
|
+
sendPasswordReset(email: string, code: string): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Sends an initial or updated recovery code to the user.
|
|
19
|
+
*/
|
|
20
|
+
sendRecoveryCode(email: string, code: string): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Optional two-factor authentication challenge delivery.
|
|
23
|
+
*/
|
|
24
|
+
send2FACode?(email: string, code: string): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* IoC Token for the authentication notification port.
|
|
28
|
+
*/
|
|
29
|
+
export declare const AUTH_NOTIFICATION_SERVICE: Token<IAuthNotificationService>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { IEventBus } from "@veap/kernel";
|
|
2
|
+
import type { IAuthNotificationService } from "./contracts.js";
|
|
3
|
+
export interface IMailerLike {
|
|
4
|
+
sendMail(message: {
|
|
5
|
+
to: string;
|
|
6
|
+
subject: string;
|
|
7
|
+
text?: string;
|
|
8
|
+
html?: string;
|
|
9
|
+
}): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export type MailerResolver = () => Promise<IMailerLike | undefined> | IMailerLike | undefined;
|
|
12
|
+
export type EventBusResolver = () => Promise<IEventBus | undefined> | IEventBus | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* Default implementation of the authentication notification port.
|
|
15
|
+
*
|
|
16
|
+
* If a mailer adapter is registered, it constructs and sends localized or
|
|
17
|
+
* templated emails. If an EventBus is available, it emits system auth events.
|
|
18
|
+
* In development or testing without a mailer, it logs codes safely.
|
|
19
|
+
*/
|
|
20
|
+
export declare class DefaultAuthNotificationService implements IAuthNotificationService {
|
|
21
|
+
private readonly mailerResolver?;
|
|
22
|
+
private readonly eventBusResolver?;
|
|
23
|
+
constructor(mailerResolver?: MailerResolver | undefined, eventBusResolver?: EventBusResolver | undefined);
|
|
24
|
+
private getMailer;
|
|
25
|
+
private getEventBus;
|
|
26
|
+
sendVerificationEmail(email: string, code: string): Promise<void>;
|
|
27
|
+
sendPasswordReset(email: string, code: string): Promise<void>;
|
|
28
|
+
sendRecoveryCode(email: string, code: string): Promise<void>;
|
|
29
|
+
send2FACode(email: string, code: string): Promise<void>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default implementation of the authentication notification port.
|
|
3
|
+
*
|
|
4
|
+
* If a mailer adapter is registered, it constructs and sends localized or
|
|
5
|
+
* templated emails. If an EventBus is available, it emits system auth events.
|
|
6
|
+
* In development or testing without a mailer, it logs codes safely.
|
|
7
|
+
*/
|
|
8
|
+
export class DefaultAuthNotificationService {
|
|
9
|
+
mailerResolver;
|
|
10
|
+
eventBusResolver;
|
|
11
|
+
constructor(mailerResolver, eventBusResolver) {
|
|
12
|
+
this.mailerResolver = mailerResolver;
|
|
13
|
+
this.eventBusResolver = eventBusResolver;
|
|
14
|
+
}
|
|
15
|
+
async getMailer() {
|
|
16
|
+
if (!this.mailerResolver)
|
|
17
|
+
return undefined;
|
|
18
|
+
return await this.mailerResolver();
|
|
19
|
+
}
|
|
20
|
+
async getEventBus() {
|
|
21
|
+
if (!this.eventBusResolver)
|
|
22
|
+
return undefined;
|
|
23
|
+
return await this.eventBusResolver();
|
|
24
|
+
}
|
|
25
|
+
async sendVerificationEmail(email, code) {
|
|
26
|
+
const mailer = await this.getMailer();
|
|
27
|
+
if (mailer) {
|
|
28
|
+
await mailer.sendMail({
|
|
29
|
+
to: email,
|
|
30
|
+
subject: "Verify Email",
|
|
31
|
+
text: `Verify your email address with your code: ${code}`,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
console.info(`[veap:auth] Verification email code for ${email}: ${code}`);
|
|
36
|
+
}
|
|
37
|
+
const eventBus = await this.getEventBus();
|
|
38
|
+
if (eventBus) {
|
|
39
|
+
await eventBus.publish("system:auth:verification-requested", {
|
|
40
|
+
email,
|
|
41
|
+
code,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async sendPasswordReset(email, code) {
|
|
46
|
+
const mailer = await this.getMailer();
|
|
47
|
+
if (mailer) {
|
|
48
|
+
await mailer.sendMail({
|
|
49
|
+
to: email,
|
|
50
|
+
subject: "Password Reset",
|
|
51
|
+
text: `Verify password reset with code: ${code}`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
console.info(`[veap:auth] Password reset code for ${email}: ${code}`);
|
|
56
|
+
}
|
|
57
|
+
const eventBus = await this.getEventBus();
|
|
58
|
+
if (eventBus) {
|
|
59
|
+
await eventBus.publish("system:auth:password-reset:requested", {
|
|
60
|
+
email,
|
|
61
|
+
code,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async sendRecoveryCode(email, code) {
|
|
66
|
+
const mailer = await this.getMailer();
|
|
67
|
+
if (mailer) {
|
|
68
|
+
await mailer.sendMail({
|
|
69
|
+
to: email,
|
|
70
|
+
subject: "Recovery Code",
|
|
71
|
+
text: `Your recovery code is: ${code}`,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
console.info(`[veap:auth] Recovery code for ${email}: ${code}`);
|
|
76
|
+
}
|
|
77
|
+
const eventBus = await this.getEventBus();
|
|
78
|
+
if (eventBus) {
|
|
79
|
+
await eventBus.publish("system:auth:recovery-code:generated", {
|
|
80
|
+
email,
|
|
81
|
+
recoveryCode: code,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async send2FACode(email, code) {
|
|
86
|
+
const mailer = await this.getMailer();
|
|
87
|
+
if (mailer) {
|
|
88
|
+
await mailer.sendMail({
|
|
89
|
+
to: email,
|
|
90
|
+
subject: "Your 2FA Code",
|
|
91
|
+
text: `Your verification code is: ${code}. It will expire in 10 minutes.`,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
console.info(`[veap:auth] 2FA verification code for ${email}: ${code}`);
|
|
96
|
+
}
|
|
97
|
+
const eventBus = await this.getEventBus();
|
|
98
|
+
if (eventBus) {
|
|
99
|
+
await eventBus.publish("system:auth:2fa-code:requested", {
|
|
100
|
+
email,
|
|
101
|
+
code,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sends a verification email code using the registered auth notification service.
|
|
3
|
+
*/
|
|
4
|
+
export declare function sendVerifyEmail(email: string, code: string): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* Sends a password reset code using the registered auth notification service.
|
|
7
|
+
*/
|
|
8
|
+
export declare function sendResetPassword(email: string, code: string): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Sends a recovery code using the registered auth notification service.
|
|
11
|
+
*/
|
|
12
|
+
export declare function sendRecoveryCode(email: string, code: string): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Sends a 2FA verification code using the registered auth notification service.
|
|
15
|
+
*/
|
|
16
|
+
export declare function send2FACode(email: string, code: string): Promise<void>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { container } from "@veap/kernel";
|
|
2
|
+
import { AUTH_NOTIFICATION_SERVICE, } from "./contracts.js";
|
|
3
|
+
import { DefaultAuthNotificationService } from "./default-notification.service.js";
|
|
4
|
+
async function getNotificationService() {
|
|
5
|
+
if (container.has(AUTH_NOTIFICATION_SERVICE)) {
|
|
6
|
+
return await container.resolve(AUTH_NOTIFICATION_SERVICE);
|
|
7
|
+
}
|
|
8
|
+
return new DefaultAuthNotificationService();
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Sends a verification email code using the registered auth notification service.
|
|
12
|
+
*/
|
|
13
|
+
export async function sendVerifyEmail(email, code) {
|
|
14
|
+
const service = await getNotificationService();
|
|
15
|
+
return service.sendVerificationEmail(email, code);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Sends a password reset code using the registered auth notification service.
|
|
19
|
+
*/
|
|
20
|
+
export async function sendResetPassword(email, code) {
|
|
21
|
+
const service = await getNotificationService();
|
|
22
|
+
return service.sendPasswordReset(email, code);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Sends a recovery code using the registered auth notification service.
|
|
26
|
+
*/
|
|
27
|
+
export async function sendRecoveryCode(email, code) {
|
|
28
|
+
const service = await getNotificationService();
|
|
29
|
+
return service.sendRecoveryCode(email, code);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Sends a 2FA verification code using the registered auth notification service.
|
|
33
|
+
*/
|
|
34
|
+
export async function send2FACode(email, code) {
|
|
35
|
+
const service = await getNotificationService();
|
|
36
|
+
if (service.send2FACode) {
|
|
37
|
+
return service.send2FACode(email, code);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
"use server";
|
|
2
1
|
import { app } from "@veap/kernel";
|
|
3
2
|
import { authContext, isAuthContextBound } from "../common/context.js";
|
|
4
3
|
import { PasswordResetService } from "./password-reset.service.js";
|
|
5
4
|
async function resolvePasswordResetService() {
|
|
5
|
+
if (isAuthContextBound()) {
|
|
6
|
+
return authContext().passwordReset;
|
|
7
|
+
}
|
|
6
8
|
const g = globalThis;
|
|
7
9
|
if (g.__VEAP_BOOTSTRAPPING_PROMISE__) {
|
|
8
10
|
try {
|
|
9
11
|
await g.__VEAP_BOOTSTRAPPING_PROMISE__;
|
|
10
12
|
}
|
|
11
13
|
catch { }
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
if (isAuthContextBound()) {
|
|
15
|
+
return authContext().passwordReset;
|
|
16
|
+
}
|
|
15
17
|
}
|
|
16
18
|
return await app(PasswordResetService);
|
|
17
19
|
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { type ICacheProvider, type ICookieStore } from "@veap/kernel";
|
|
2
2
|
import type { PasswordResetAuthSession, PasswordResetSession as PasswordResetSessionType } from "../common/index.js";
|
|
3
3
|
import { type ITokenGenerator } from "../crypto/contracts.js";
|
|
4
|
+
import { type IAuthNotificationService } from "../notifications/contracts.js";
|
|
4
5
|
import { type IPasswordResetRepository } from "./contracts.js";
|
|
5
6
|
export declare class PasswordResetService {
|
|
6
7
|
private readonly sessions;
|
|
7
8
|
private readonly tokens;
|
|
8
9
|
private readonly cookieStore;
|
|
9
10
|
private readonly cache?;
|
|
11
|
+
private readonly notifier?;
|
|
10
12
|
private inMemoryAttempts;
|
|
11
|
-
constructor(sessions: IPasswordResetRepository, tokens: ITokenGenerator, cookieStore: ICookieStore, cache?: ICacheProvider | undefined);
|
|
13
|
+
constructor(sessions: IPasswordResetRepository, tokens: ITokenGenerator, cookieStore: ICookieStore, cache?: ICacheProvider | undefined, notifier?: IAuthNotificationService | undefined);
|
|
12
14
|
createPasswordResetSession(token: string, userId: string, email: string): Promise<PasswordResetSessionType>;
|
|
13
15
|
/**
|
|
14
16
|
* Creates an ephemeral dummy reset session for an unregistered email.
|
|
@@ -11,11 +11,11 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
11
11
|
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
12
|
};
|
|
13
13
|
import { CACHE_PROVIDER, COOKIE_STORE, Inject, Injectable, } from "@veap/kernel";
|
|
14
|
-
import { sendResetPassword } from "@veap/mail";
|
|
15
14
|
import { addMinutes } from "date-fns";
|
|
16
15
|
import { augmentPasswordResetSession } from "../common/augment.js";
|
|
17
16
|
import { performFullUserAugmentation } from "../common/logic.js";
|
|
18
17
|
import { TOKEN_GENERATOR } from "../crypto/contracts.js";
|
|
18
|
+
import { AUTH_NOTIFICATION_SERVICE, } from "../notifications/contracts.js";
|
|
19
19
|
import { PASSWORD_RESET_REPOSITORY, } from "./contracts.js";
|
|
20
20
|
const dummySessions = new Map();
|
|
21
21
|
let PasswordResetService = class PasswordResetService {
|
|
@@ -23,12 +23,14 @@ let PasswordResetService = class PasswordResetService {
|
|
|
23
23
|
tokens;
|
|
24
24
|
cookieStore;
|
|
25
25
|
cache;
|
|
26
|
+
notifier;
|
|
26
27
|
inMemoryAttempts = new Map();
|
|
27
|
-
constructor(sessions, tokens, cookieStore, cache) {
|
|
28
|
+
constructor(sessions, tokens, cookieStore, cache, notifier) {
|
|
28
29
|
this.sessions = sessions;
|
|
29
30
|
this.tokens = tokens;
|
|
30
31
|
this.cookieStore = cookieStore;
|
|
31
32
|
this.cache = cache;
|
|
33
|
+
this.notifier = notifier;
|
|
32
34
|
}
|
|
33
35
|
async createPasswordResetSession(token, userId, email) {
|
|
34
36
|
const sessionId = this.tokens.hashToken(token);
|
|
@@ -228,7 +230,9 @@ let PasswordResetService = class PasswordResetService {
|
|
|
228
230
|
return true;
|
|
229
231
|
}
|
|
230
232
|
async sendPasswordResetEmail(email, code) {
|
|
231
|
-
|
|
233
|
+
if (this.notifier) {
|
|
234
|
+
await this.notifier.sendPasswordReset(email, code);
|
|
235
|
+
}
|
|
232
236
|
}
|
|
233
237
|
};
|
|
234
238
|
PasswordResetService = __decorate([
|
|
@@ -237,6 +241,7 @@ PasswordResetService = __decorate([
|
|
|
237
241
|
__param(1, Inject(TOKEN_GENERATOR)),
|
|
238
242
|
__param(2, Inject(COOKIE_STORE)),
|
|
239
243
|
__param(3, Inject(CACHE_PROVIDER)),
|
|
240
|
-
|
|
244
|
+
__param(4, Inject(AUTH_NOTIFICATION_SERVICE)),
|
|
245
|
+
__metadata("design:paramtypes", [Object, Object, Object, Object, Object])
|
|
241
246
|
], PasswordResetService);
|
|
242
247
|
export { PasswordResetService };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isDatabaseInitialized } from "@veap/database";
|
|
2
|
-
import { ConfigService, Container, container as defaultContainer, logger, ServiceProvider, } from "@veap/kernel";
|
|
2
|
+
import { ConfigService, Container, container as defaultContainer, EVENT_BUS, logger, ServiceProvider, } from "@veap/kernel";
|
|
3
3
|
import { bindAuthContext } from "../common/context.js";
|
|
4
4
|
import { AUTH_ROUTES, DEFAULT_AUTH_ROUTES, ROUTER_SERVICE, } from "../common/routes.js";
|
|
5
5
|
import { AesSecretCipher } from "../crypto/aes-cipher.js";
|
|
@@ -7,6 +7,7 @@ import { BcryptPasswordHasher } from "../crypto/bcrypt-hasher.js";
|
|
|
7
7
|
import { PASSWORD_HASHER, SECRET_CIPHER, TOKEN_GENERATOR, } from "../crypto/contracts.js";
|
|
8
8
|
import { OsloTokenGenerator } from "../crypto/token-generator.js";
|
|
9
9
|
import { runAuthMigrations } from "../migrations/index.js";
|
|
10
|
+
import { AUTH_NOTIFICATION_SERVICE, DefaultAuthNotificationService, } from "../notifications/index.js";
|
|
10
11
|
import { PASSWORD_RESET_REPOSITORY } from "../passwords/contracts.js";
|
|
11
12
|
import { ActiveRecordPasswordResetRepository } from "../passwords/password-reset.repository.js";
|
|
12
13
|
import { PasswordResetService } from "../passwords/password-reset.service.js";
|
|
@@ -103,6 +104,20 @@ export class AuthServiceProvider extends ServiceProvider {
|
|
|
103
104
|
useClass: ActiveRecordEmailVerificationRepository,
|
|
104
105
|
singleton: true,
|
|
105
106
|
});
|
|
107
|
+
if (!this.container.has(AUTH_NOTIFICATION_SERVICE)) {
|
|
108
|
+
this.container.register({
|
|
109
|
+
token: AUTH_NOTIFICATION_SERVICE,
|
|
110
|
+
useFactory: () => {
|
|
111
|
+
const mailerToken = Symbol.for("veap:communication:mailer");
|
|
112
|
+
return new DefaultAuthNotificationService(() => this.container.has(mailerToken)
|
|
113
|
+
? this.container.resolve(mailerToken)
|
|
114
|
+
: undefined, () => this.container.has(EVENT_BUS)
|
|
115
|
+
? this.container.resolve(EVENT_BUS)
|
|
116
|
+
: undefined);
|
|
117
|
+
},
|
|
118
|
+
singleton: true,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
106
121
|
// Services
|
|
107
122
|
this.container.register({
|
|
108
123
|
token: UserService,
|
|
@@ -138,7 +153,7 @@ export class AuthServiceProvider extends ServiceProvider {
|
|
|
138
153
|
async boot() {
|
|
139
154
|
// 0. Auto-bootstrap setup state
|
|
140
155
|
try {
|
|
141
|
-
isSystemInstalled();
|
|
156
|
+
await isSystemInstalled();
|
|
142
157
|
}
|
|
143
158
|
catch { }
|
|
144
159
|
// 1. Resolve domain services and bind singleton auth context
|
|
@@ -148,6 +163,7 @@ export class AuthServiceProvider extends ServiceProvider {
|
|
|
148
163
|
const passwordReset = await this.container.resolve(PasswordResetService);
|
|
149
164
|
const emailVerification = await this.container.resolve(EmailVerificationService);
|
|
150
165
|
const auth = await this.container.resolve(AuthService);
|
|
166
|
+
const passwordHasher = await this.container.resolve(PASSWORD_HASHER);
|
|
151
167
|
bindAuthContext({
|
|
152
168
|
user,
|
|
153
169
|
session,
|
|
@@ -155,6 +171,7 @@ export class AuthServiceProvider extends ServiceProvider {
|
|
|
155
171
|
passwordReset,
|
|
156
172
|
emailVerification,
|
|
157
173
|
auth,
|
|
174
|
+
passwordHasher,
|
|
158
175
|
routes: this.resolveRoutes(),
|
|
159
176
|
});
|
|
160
177
|
// 2. Register route rewrites if RouterService is available
|
package/dist/providers/setup.js
CHANGED
|
@@ -8,7 +8,18 @@ import { User } from "../models/User.js";
|
|
|
8
8
|
*/
|
|
9
9
|
export async function isSystemInstalled() {
|
|
10
10
|
try {
|
|
11
|
-
const
|
|
11
|
+
const res = await User.query().count();
|
|
12
|
+
let count = 0;
|
|
13
|
+
if (Array.isArray(res) && res.length > 0) {
|
|
14
|
+
const first = res[0];
|
|
15
|
+
const rawVal = typeof first === "object" && first !== null
|
|
16
|
+
? Object.values(first)[0]
|
|
17
|
+
: first;
|
|
18
|
+
count = Number(rawVal) || 0;
|
|
19
|
+
}
|
|
20
|
+
else if (typeof res === "number") {
|
|
21
|
+
count = res;
|
|
22
|
+
}
|
|
12
23
|
if (count === 0) {
|
|
13
24
|
logger.debug("veap:setup", "System not installed. Publishing event.");
|
|
14
25
|
await eventBus.publish("system:not-installed", { timestamp: Date.now() });
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export declare function getRoles(): Promise<import("./contracts.js").RoleRecord[]>;
|
|
2
2
|
export declare function getRoleById(roleId: string): Promise<import("./contracts.js").RoleRecord | undefined>;
|
|
3
|
-
export declare function createRole(name: string, description?: string): Promise<import("./contracts.js").RoleRecord
|
|
3
|
+
export declare function createRole(name: string, description?: string): Promise<import("./contracts.js").RoleRecord>;
|
|
4
4
|
export declare function deleteRole(roleId: string): Promise<void>;
|
|
5
5
|
export declare function getPermissions(): Promise<import("./contracts.js").PermissionRecord[]>;
|
|
6
6
|
export declare function getPermissionById(permissionId: string): Promise<import("./contracts.js").PermissionRecord | undefined>;
|
|
7
|
-
export declare function createPermission(name: string, description?: string): Promise<import("./contracts.js").PermissionRecord
|
|
7
|
+
export declare function createPermission(name: string, description?: string): Promise<import("./contracts.js").PermissionRecord>;
|
|
8
8
|
export declare function deletePermission(permissionId: string): Promise<void>;
|
|
9
9
|
export declare function getRolePermissions(roleId: string): Promise<import("./contracts.js").AccessGrant[]>;
|
|
10
10
|
export declare function assignPermissionToRole(roleId: string, permissionId: string): Promise<void>;
|
package/dist/rbac/rbac.facade.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
"use server";
|
|
2
1
|
import { app } from "@veap/kernel";
|
|
3
2
|
import { authContext, isAuthContextBound } from "../common/context.js";
|
|
4
3
|
import { RbacService } from "./rbac.service.js";
|
|
5
4
|
async function resolveRbacService() {
|
|
5
|
+
if (isAuthContextBound()) {
|
|
6
|
+
return authContext().rbac;
|
|
7
|
+
}
|
|
6
8
|
const g = globalThis;
|
|
7
9
|
if (g.__VEAP_BOOTSTRAPPING_PROMISE__) {
|
|
8
10
|
try {
|
|
9
11
|
await g.__VEAP_BOOTSTRAPPING_PROMISE__;
|
|
10
12
|
}
|
|
11
13
|
catch { }
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
if (isAuthContextBound()) {
|
|
15
|
+
return authContext().rbac;
|
|
16
|
+
}
|
|
15
17
|
}
|
|
16
18
|
return await app(RbacService);
|
|
17
19
|
}
|
|
@@ -10,11 +10,11 @@ export declare class RbacService {
|
|
|
10
10
|
constructor(roles: IRoleRepository, permissions: IPermissionRepository);
|
|
11
11
|
getRoles(): Promise<RoleRecord[]>;
|
|
12
12
|
getRoleById(roleId: string): Promise<RoleRecord | undefined>;
|
|
13
|
-
createRole(name: string, description?: string): Promise<RoleRecord
|
|
13
|
+
createRole(name: string, description?: string): Promise<RoleRecord>;
|
|
14
14
|
deleteRole(roleId: string): Promise<void>;
|
|
15
15
|
getPermissions(): Promise<PermissionRecord[]>;
|
|
16
16
|
getPermissionById(permissionId: string): Promise<PermissionRecord | undefined>;
|
|
17
|
-
createPermission(name: string, description?: string): Promise<PermissionRecord
|
|
17
|
+
createPermission(name: string, description?: string): Promise<PermissionRecord>;
|
|
18
18
|
deletePermission(permissionId: string): Promise<void>;
|
|
19
19
|
getRolePermissions(roleId: string): Promise<AccessGrant[]>;
|
|
20
20
|
assignPermissionToRole(roleId: string, permissionId: string): Promise<void>;
|
|
@@ -31,7 +31,7 @@ let RbacService = class RbacService {
|
|
|
31
31
|
}
|
|
32
32
|
async createRole(name, description) {
|
|
33
33
|
const role = await this.roles.create({ name, description });
|
|
34
|
-
return
|
|
34
|
+
return role;
|
|
35
35
|
}
|
|
36
36
|
async deleteRole(roleId) {
|
|
37
37
|
await this.roles.remove(roleId);
|
|
@@ -44,7 +44,7 @@ let RbacService = class RbacService {
|
|
|
44
44
|
}
|
|
45
45
|
async createPermission(name, description) {
|
|
46
46
|
const permission = await this.permissions.create({ name, description });
|
|
47
|
-
return
|
|
47
|
+
return permission;
|
|
48
48
|
}
|
|
49
49
|
async deletePermission(permissionId) {
|
|
50
50
|
await this.permissions.remove(permissionId);
|
package/dist/server.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
export * from "./actions/index.js";
|
|
2
|
+
export * from "./common/index.js";
|
|
3
|
+
export * from "./composition.js";
|
|
2
4
|
export * from "./crypto/index.js";
|
|
3
|
-
export * from "./index.js";
|
|
5
|
+
export * from "./notifications/index.js";
|
|
6
|
+
export * from "./passwords/index.js";
|
|
7
|
+
export * from "./providers/index.js";
|
|
8
|
+
export * from "./rbac/index.js";
|
|
9
|
+
export * from "./session/index.js";
|
|
10
|
+
export * from "./users/index.js";
|
|
11
|
+
export * from "./validation/index.js";
|
|
12
|
+
export * from "./verification/index.js";
|
package/dist/server.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
export * from "./actions/index.js";
|
|
2
|
+
export * from "./common/index.js";
|
|
3
|
+
export * from "./composition.js";
|
|
2
4
|
export * from "./crypto/index.js";
|
|
3
|
-
export * from "./index.js";
|
|
5
|
+
export * from "./notifications/index.js";
|
|
6
|
+
export * from "./passwords/index.js";
|
|
7
|
+
export * from "./providers/index.js";
|
|
8
|
+
export * from "./rbac/index.js";
|
|
9
|
+
export * from "./session/index.js";
|
|
10
|
+
export * from "./users/index.js";
|
|
11
|
+
export * from "./validation/index.js";
|
|
12
|
+
export * from "./verification/index.js";
|
|
@@ -128,6 +128,7 @@ let AuthService = class AuthService {
|
|
|
128
128
|
status: "SUCCESS",
|
|
129
129
|
session: { ...session },
|
|
130
130
|
user: { ...fullUser },
|
|
131
|
+
token: sessionToken,
|
|
131
132
|
};
|
|
132
133
|
}
|
|
133
134
|
/**
|
|
@@ -162,6 +163,7 @@ let AuthService = class AuthService {
|
|
|
162
163
|
status: "SUCCESS",
|
|
163
164
|
session: { ...session },
|
|
164
165
|
user: { ...fullUser },
|
|
166
|
+
token: sessionToken,
|
|
165
167
|
};
|
|
166
168
|
}
|
|
167
169
|
/**
|
|
@@ -184,6 +186,7 @@ let AuthService = class AuthService {
|
|
|
184
186
|
status: "SUCCESS",
|
|
185
187
|
session: { ...session },
|
|
186
188
|
user: { ...fullUser },
|
|
189
|
+
token: sessionToken,
|
|
187
190
|
};
|
|
188
191
|
}
|
|
189
192
|
/**
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
"use server";
|
|
2
1
|
import { app } from "@veap/kernel";
|
|
3
2
|
import { cache } from "react";
|
|
4
3
|
import { authContext, isAuthContextBound } from "../common/context.js";
|
|
5
4
|
import { SessionService } from "./session.service.js";
|
|
6
5
|
async function resolveSessionService() {
|
|
6
|
+
if (isAuthContextBound()) {
|
|
7
|
+
return authContext().session;
|
|
8
|
+
}
|
|
7
9
|
const g = globalThis;
|
|
8
10
|
if (g.__VEAP_BOOTSTRAPPING_PROMISE__) {
|
|
9
11
|
try {
|
|
10
12
|
await g.__VEAP_BOOTSTRAPPING_PROMISE__;
|
|
11
13
|
}
|
|
12
14
|
catch { }
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
if (isAuthContextBound()) {
|
|
16
|
+
return authContext().session;
|
|
17
|
+
}
|
|
16
18
|
}
|
|
17
19
|
return await app(SessionService);
|
|
18
20
|
}
|
|
@@ -17,7 +17,7 @@ export declare class SessionService {
|
|
|
17
17
|
*/
|
|
18
18
|
validateSessionToken(token: string): Promise<AuthSession>;
|
|
19
19
|
/**
|
|
20
|
-
* Returns the current user session from cookies.
|
|
20
|
+
* Returns the current user session from Bearer token or cookies.
|
|
21
21
|
*/
|
|
22
22
|
getCurrentSession(): Promise<AuthSession>;
|
|
23
23
|
/**
|
|
@@ -94,11 +94,22 @@ let SessionService = class SessionService {
|
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
96
|
/**
|
|
97
|
-
* Returns the current user session from cookies.
|
|
97
|
+
* Returns the current user session from Bearer token or cookies.
|
|
98
98
|
*/
|
|
99
99
|
async getCurrentSession() {
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
let token = null;
|
|
101
|
+
const authHeader = (await this.requestContext.getHeader("authorization")) ??
|
|
102
|
+
(await this.requestContext.getHeader("Authorization"));
|
|
103
|
+
if (authHeader) {
|
|
104
|
+
const match = authHeader.match(/^Bearer\s+(.+)$/i);
|
|
105
|
+
if (match?.[1]) {
|
|
106
|
+
token = match[1].trim();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (!token) {
|
|
110
|
+
token = await this.cookieStore.get("session");
|
|
111
|
+
}
|
|
112
|
+
if (token === null || token === "") {
|
|
102
113
|
return { session: null, user: null };
|
|
103
114
|
}
|
|
104
115
|
return await this.validateSessionToken(token);
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
"use server";
|
|
2
1
|
import { app } from "@veap/kernel";
|
|
3
2
|
import { authContext, isAuthContextBound } from "../common/context.js";
|
|
4
3
|
import { UserService } from "./user.service.js";
|
|
5
4
|
async function resolveUserService() {
|
|
5
|
+
if (isAuthContextBound()) {
|
|
6
|
+
return authContext().user;
|
|
7
|
+
}
|
|
6
8
|
const g = globalThis;
|
|
7
9
|
if (g.__VEAP_BOOTSTRAPPING_PROMISE__) {
|
|
8
10
|
try {
|
|
9
11
|
await g.__VEAP_BOOTSTRAPPING_PROMISE__;
|
|
10
12
|
}
|
|
11
13
|
catch { }
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
if (isAuthContextBound()) {
|
|
15
|
+
return authContext().user;
|
|
16
|
+
}
|
|
15
17
|
}
|
|
16
18
|
return await app(UserService);
|
|
17
19
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { User } from "../common/index.js";
|
|
2
2
|
import { type IPasswordHasher, type ISecretCipher, type ITokenGenerator } from "../crypto/contracts.js";
|
|
3
|
+
import { type IAuthNotificationService } from "../notifications/contracts.js";
|
|
3
4
|
import { type IRoleRepository } from "../rbac/contracts.js";
|
|
4
5
|
import { type IUserRepository } from "./contracts.js";
|
|
5
6
|
export declare class UserService {
|
|
@@ -8,7 +9,8 @@ export declare class UserService {
|
|
|
8
9
|
private readonly hasher;
|
|
9
10
|
private readonly tokens;
|
|
10
11
|
private readonly cipher;
|
|
11
|
-
|
|
12
|
+
private readonly notifier?;
|
|
13
|
+
constructor(users: IUserRepository, roles: IRoleRepository, hasher: IPasswordHasher, tokens: ITokenGenerator, cipher: ISecretCipher, notifier?: IAuthNotificationService | undefined);
|
|
12
14
|
/**
|
|
13
15
|
* Validates the username input.
|
|
14
16
|
*/
|
|
@@ -11,8 +11,8 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
11
11
|
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
12
|
};
|
|
13
13
|
import { AppError, Inject, Injectable } from "@veap/kernel";
|
|
14
|
-
import { sendRecoveryCode } from "@veap/mail";
|
|
15
14
|
import { PASSWORD_HASHER, SECRET_CIPHER, TOKEN_GENERATOR, } from "../crypto/contracts.js";
|
|
15
|
+
import { AUTH_NOTIFICATION_SERVICE, } from "../notifications/contracts.js";
|
|
16
16
|
import { ROLE_REPOSITORY } from "../rbac/contracts.js";
|
|
17
17
|
import { USER_REPOSITORY } from "./contracts.js";
|
|
18
18
|
const DEFAULT_ROLE = "user";
|
|
@@ -22,12 +22,14 @@ let UserService = class UserService {
|
|
|
22
22
|
hasher;
|
|
23
23
|
tokens;
|
|
24
24
|
cipher;
|
|
25
|
-
|
|
25
|
+
notifier;
|
|
26
|
+
constructor(users, roles, hasher, tokens, cipher, notifier) {
|
|
26
27
|
this.users = users;
|
|
27
28
|
this.roles = roles;
|
|
28
29
|
this.hasher = hasher;
|
|
29
30
|
this.tokens = tokens;
|
|
30
31
|
this.cipher = cipher;
|
|
32
|
+
this.notifier = notifier;
|
|
31
33
|
}
|
|
32
34
|
/**
|
|
33
35
|
* Validates the username input.
|
|
@@ -57,7 +59,9 @@ let UserService = class UserService {
|
|
|
57
59
|
});
|
|
58
60
|
await this.assignDefaultRole(user.id);
|
|
59
61
|
try {
|
|
60
|
-
|
|
62
|
+
if (this.notifier) {
|
|
63
|
+
await this.notifier.sendRecoveryCode(user.email, recoveryCode);
|
|
64
|
+
}
|
|
61
65
|
}
|
|
62
66
|
catch (error) {
|
|
63
67
|
console.warn(`[veap:auth] Failed to send recovery code email to ${user.email}:`, error?.message || error);
|
|
@@ -112,7 +116,9 @@ let UserService = class UserService {
|
|
|
112
116
|
const recoveryCode = this.tokens.generateRecoveryCode();
|
|
113
117
|
await this.users.setRecoveryCode(userId, this.cipher.encrypt(recoveryCode));
|
|
114
118
|
try {
|
|
115
|
-
|
|
119
|
+
if (this.notifier) {
|
|
120
|
+
await this.notifier.sendRecoveryCode(user.email, recoveryCode);
|
|
121
|
+
}
|
|
116
122
|
}
|
|
117
123
|
catch (error) {
|
|
118
124
|
console.warn(`[veap:auth] Failed to send recovery code email to ${user.email}:`, error?.message || error);
|
|
@@ -196,6 +202,7 @@ UserService = __decorate([
|
|
|
196
202
|
__param(2, Inject(PASSWORD_HASHER)),
|
|
197
203
|
__param(3, Inject(TOKEN_GENERATOR)),
|
|
198
204
|
__param(4, Inject(SECRET_CIPHER)),
|
|
199
|
-
|
|
205
|
+
__param(5, Inject(AUTH_NOTIFICATION_SERVICE)),
|
|
206
|
+
__metadata("design:paramtypes", [Object, Object, Object, Object, Object, Object])
|
|
200
207
|
], UserService);
|
|
201
208
|
export { UserService };
|
package/dist/validation/index.js
CHANGED
|
@@ -12,7 +12,7 @@ export const loginSchema = z.object({
|
|
|
12
12
|
remember: z.boolean().optional(),
|
|
13
13
|
});
|
|
14
14
|
export const registerSchema = z.object({
|
|
15
|
-
username: z.string().min(3, "Name must be at least
|
|
15
|
+
username: z.string().min(3, "Name must be at least 3 characters"),
|
|
16
16
|
email: z.string().email("Invalid email address"),
|
|
17
17
|
password: z.string().min(8, "Password must be at least 8 characters"),
|
|
18
18
|
terms: z
|
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
"use server";
|
|
2
1
|
import { app } from "@veap/kernel";
|
|
3
2
|
import { authContext, isAuthContextBound } from "../common/context.js";
|
|
4
3
|
import { EmailVerificationService, } from "./verification.service.js";
|
|
5
4
|
async function resolveVerificationService() {
|
|
5
|
+
if (isAuthContextBound()) {
|
|
6
|
+
return authContext().emailVerification;
|
|
7
|
+
}
|
|
6
8
|
const g = globalThis;
|
|
7
9
|
if (g.__VEAP_BOOTSTRAPPING_PROMISE__) {
|
|
8
10
|
try {
|
|
9
11
|
await g.__VEAP_BOOTSTRAPPING_PROMISE__;
|
|
10
12
|
}
|
|
11
13
|
catch { }
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
if (isAuthContextBound()) {
|
|
15
|
+
return authContext().emailVerification;
|
|
16
|
+
}
|
|
15
17
|
}
|
|
16
18
|
return await app(EmailVerificationService);
|
|
17
19
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ICookieStore } from "@veap/kernel";
|
|
2
2
|
import { type ITokenGenerator } from "../crypto/contracts.js";
|
|
3
|
+
import { type IAuthNotificationService } from "../notifications/contracts.js";
|
|
3
4
|
import { SessionService } from "../session/session.service.js";
|
|
4
5
|
import { type EmailVerificationRecord, type IEmailVerificationRepository } from "./contracts.js";
|
|
5
6
|
export type EmailVerificationRequestType = EmailVerificationRecord;
|
|
@@ -8,7 +9,8 @@ export declare class EmailVerificationService {
|
|
|
8
9
|
private readonly tokens;
|
|
9
10
|
private readonly cookieStore;
|
|
10
11
|
private readonly sessions;
|
|
11
|
-
|
|
12
|
+
private readonly notifier?;
|
|
13
|
+
constructor(verifications: IEmailVerificationRepository, tokens: ITokenGenerator, cookieStore: ICookieStore, sessions: SessionService, notifier?: IAuthNotificationService | undefined);
|
|
12
14
|
/**
|
|
13
15
|
* Register Email Verification as a Core Security Requirement.
|
|
14
16
|
*/
|
|
@@ -11,11 +11,11 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
11
11
|
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
12
|
};
|
|
13
13
|
import { COOKIE_STORE, Inject, Injectable, } from "@veap/kernel";
|
|
14
|
-
import { sendVerifyEmail } from "@veap/mail";
|
|
15
14
|
import { addMinutes } from "date-fns";
|
|
16
15
|
import { getAuthRoutes } from "../common/context.js";
|
|
17
16
|
import { registerSecurityRequirement } from "../common/logic.js";
|
|
18
17
|
import { TOKEN_GENERATOR } from "../crypto/contracts.js";
|
|
18
|
+
import { AUTH_NOTIFICATION_SERVICE, } from "../notifications/contracts.js";
|
|
19
19
|
import { SessionService } from "../session/session.service.js";
|
|
20
20
|
import { EMAIL_VERIFICATION_REPOSITORY, } from "./contracts.js";
|
|
21
21
|
let EmailVerificationService = class EmailVerificationService {
|
|
@@ -23,11 +23,13 @@ let EmailVerificationService = class EmailVerificationService {
|
|
|
23
23
|
tokens;
|
|
24
24
|
cookieStore;
|
|
25
25
|
sessions;
|
|
26
|
-
|
|
26
|
+
notifier;
|
|
27
|
+
constructor(verifications, tokens, cookieStore, sessions, notifier) {
|
|
27
28
|
this.verifications = verifications;
|
|
28
29
|
this.tokens = tokens;
|
|
29
30
|
this.cookieStore = cookieStore;
|
|
30
31
|
this.sessions = sessions;
|
|
32
|
+
this.notifier = notifier;
|
|
31
33
|
}
|
|
32
34
|
/**
|
|
33
35
|
* Register Email Verification as a Core Security Requirement.
|
|
@@ -72,7 +74,9 @@ let EmailVerificationService = class EmailVerificationService {
|
|
|
72
74
|
* Sends a verification email with the OTP code.
|
|
73
75
|
*/
|
|
74
76
|
async sendVerificationEmail(email, code) {
|
|
75
|
-
|
|
77
|
+
if (this.notifier) {
|
|
78
|
+
await this.notifier.sendVerificationEmail(email, code);
|
|
79
|
+
}
|
|
76
80
|
}
|
|
77
81
|
/**
|
|
78
82
|
* Sets the email verification request ID in a cookie.
|
|
@@ -117,6 +121,7 @@ EmailVerificationService = __decorate([
|
|
|
117
121
|
__param(1, Inject(TOKEN_GENERATOR)),
|
|
118
122
|
__param(2, Inject(COOKIE_STORE)),
|
|
119
123
|
__param(3, Inject(SessionService)),
|
|
120
|
-
|
|
124
|
+
__param(4, Inject(AUTH_NOTIFICATION_SERVICE)),
|
|
125
|
+
__metadata("design:paramtypes", [Object, Object, Object, SessionService, Object])
|
|
121
126
|
], EmailVerificationService);
|
|
122
127
|
export { EmailVerificationService };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veap/auth",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Authentication, session management, RBAC, and crypto adapters for Veap",
|
|
5
5
|
"bugs": "https://github.com/veapjs/veap-auth/issues",
|
|
6
6
|
"repository": {
|
|
@@ -55,9 +55,8 @@
|
|
|
55
55
|
"@oslojs/binary": "^1.0.0",
|
|
56
56
|
"@oslojs/crypto": "^1.0.1",
|
|
57
57
|
"@oslojs/encoding": "^1.1.0",
|
|
58
|
-
"@veap/database": "0.1.
|
|
59
|
-
"@veap/kernel": "0.1
|
|
60
|
-
"@veap/mail": "0.1.6",
|
|
58
|
+
"@veap/database": "0.1.8",
|
|
59
|
+
"@veap/kernel": "0.2.1",
|
|
61
60
|
"bcryptjs": "^3.0.3",
|
|
62
61
|
"date-fns": "^4.4.0",
|
|
63
62
|
"zod": "^3.24.2"
|