@veap/auth 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +167 -2
- package/dist/actions/auth.actions.d.ts +23 -0
- package/dist/actions/auth.actions.js +40 -0
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/client/auth.context.d.ts +29 -0
- package/dist/client/auth.context.js +64 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -0
- package/dist/common/augment.d.ts +26 -0
- package/dist/common/augment.js +63 -0
- package/dist/common/context.d.ts +30 -0
- package/dist/common/context.js +36 -0
- package/dist/common/index.d.ts +7 -0
- package/dist/common/index.js +7 -0
- package/dist/common/logic.d.ts +64 -0
- package/dist/common/logic.js +133 -0
- package/dist/common/registry.d.ts +28 -0
- package/dist/common/registry.js +76 -0
- package/dist/common/routes.d.ts +3 -0
- package/dist/common/routes.js +2 -0
- package/dist/common/tokens.d.ts +10 -0
- package/dist/common/tokens.js +8 -0
- package/dist/common/types.d.ts +178 -0
- package/dist/common/types.js +9 -0
- package/dist/crypto/aes-cipher.d.ts +8 -0
- package/dist/crypto/aes-cipher.js +12 -0
- package/dist/crypto/bcrypt-hasher.d.ts +12 -0
- package/dist/crypto/bcrypt-hasher.js +37 -0
- package/dist/crypto/contracts.d.ts +36 -0
- package/dist/crypto/contracts.js +3 -0
- package/dist/crypto/encryption.d.ts +18 -0
- package/dist/crypto/encryption.js +69 -0
- package/dist/crypto/index.d.ts +6 -0
- package/dist/crypto/index.js +6 -0
- package/dist/crypto/password.d.ts +19 -0
- package/dist/crypto/password.js +27 -0
- package/dist/crypto/token-generator.d.ts +10 -0
- package/dist/crypto/token-generator.js +25 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/migrations/0001_create_auth_tables.d.ts +4 -0
- package/dist/migrations/0001_create_auth_tables.js +122 -0
- package/dist/migrations/index.d.ts +4 -0
- package/dist/migrations/index.js +7 -0
- package/dist/models/EmailVerification.d.ts +23 -0
- package/dist/models/EmailVerification.js +19 -0
- package/dist/models/PasswordResetSession.d.ts +25 -0
- package/dist/models/PasswordResetSession.js +21 -0
- package/dist/models/Permission.d.ts +20 -0
- package/dist/models/Permission.js +19 -0
- package/dist/models/Role.d.ts +20 -0
- package/dist/models/Role.js +19 -0
- package/dist/models/Session.d.ts +22 -0
- package/dist/models/Session.js +21 -0
- package/dist/models/User.d.ts +32 -0
- package/dist/models/User.js +31 -0
- package/dist/models/index.d.ts +6 -0
- package/dist/models/index.js +6 -0
- package/dist/models.d.ts +1 -0
- package/dist/models.js +1 -0
- package/dist/passwords/contracts.d.ts +28 -0
- package/dist/passwords/contracts.js +2 -0
- package/dist/passwords/index.d.ts +4 -0
- package/dist/passwords/index.js +4 -0
- package/dist/passwords/password-reset.facade.d.ts +14 -0
- package/dist/passwords/password-reset.facade.js +57 -0
- package/dist/passwords/password-reset.repository.d.ts +10 -0
- package/dist/passwords/password-reset.repository.js +39 -0
- package/dist/passwords/password-reset.service.d.ts +38 -0
- package/dist/passwords/password-reset.service.js +242 -0
- package/dist/providers/auth.provider.d.ts +9 -0
- package/dist/providers/auth.provider.js +199 -0
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.js +2 -0
- package/dist/providers/setup.d.ts +7 -0
- package/dist/providers/setup.js +29 -0
- package/dist/rbac/contracts.d.ts +75 -0
- package/dist/rbac/contracts.js +4 -0
- package/dist/rbac/index.d.ts +4 -0
- package/dist/rbac/index.js +4 -0
- package/dist/rbac/rbac.facade.d.ts +23 -0
- package/dist/rbac/rbac.facade.js +85 -0
- package/dist/rbac/rbac.repository.d.ts +30 -0
- package/dist/rbac/rbac.repository.js +137 -0
- package/dist/rbac/rbac.service.d.ts +35 -0
- package/dist/rbac/rbac.service.js +143 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +3 -0
- package/dist/session/auth.service.d.ts +35 -0
- package/dist/session/auth.service.js +207 -0
- package/dist/session/contracts.d.ts +33 -0
- package/dist/session/contracts.js +2 -0
- package/dist/session/index.d.ts +5 -0
- package/dist/session/index.js +5 -0
- package/dist/session/session.facade.d.ts +14 -0
- package/dist/session/session.facade.js +75 -0
- package/dist/session/session.repository.d.ts +15 -0
- package/dist/session/session.repository.js +55 -0
- package/dist/session/session.service.d.ts +63 -0
- package/dist/session/session.service.js +221 -0
- package/dist/users/contracts.d.ts +33 -0
- package/dist/users/contracts.js +2 -0
- package/dist/users/index.d.ts +4 -0
- package/dist/users/index.js +4 -0
- package/dist/users/user.facade.d.ts +28 -0
- package/dist/users/user.facade.js +92 -0
- package/dist/users/user.repository.d.ts +16 -0
- package/dist/users/user.repository.js +60 -0
- package/dist/users/user.service.d.ts +80 -0
- package/dist/users/user.service.js +201 -0
- package/dist/validation/index.d.ts +90 -0
- package/dist/validation/index.js +46 -0
- package/dist/verification/contracts.d.ts +25 -0
- package/dist/verification/contracts.js +2 -0
- package/dist/verification/index.d.ts +4 -0
- package/dist/verification/index.js +4 -0
- package/dist/verification/verification.facade.d.ts +10 -0
- package/dist/verification/verification.facade.js +49 -0
- package/dist/verification/verification.repository.d.ts +7 -0
- package/dist/verification/verification.repository.js +23 -0
- package/dist/verification/verification.service.d.ts +44 -0
- package/dist/verification/verification.service.js +122 -0
- package/package.json +84 -4
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
import { CACHE_PROVIDER, eventBus, Inject, Injectable, } from "@veap/kernel";
|
|
14
|
+
import { authValidators, performFullUserAugmentation, } from "../common/logic.js";
|
|
15
|
+
import { PASSWORD_HASHER } from "../crypto/contracts.js";
|
|
16
|
+
import { UserService } from "../users/user.service.js";
|
|
17
|
+
import { loginSchema, registerSchema } from "../validation/index.js";
|
|
18
|
+
import { EmailVerificationService } from "../verification/verification.service.js";
|
|
19
|
+
import { SessionService } from "./session.service.js";
|
|
20
|
+
let AuthService = class AuthService {
|
|
21
|
+
userService;
|
|
22
|
+
sessionService;
|
|
23
|
+
emailVerificationService;
|
|
24
|
+
hasher;
|
|
25
|
+
cache;
|
|
26
|
+
failedAttempts = new Map();
|
|
27
|
+
constructor(userService, sessionService, emailVerificationService, hasher, cache) {
|
|
28
|
+
this.userService = userService;
|
|
29
|
+
this.sessionService = sessionService;
|
|
30
|
+
this.emailVerificationService = emailVerificationService;
|
|
31
|
+
this.hasher = hasher;
|
|
32
|
+
this.cache = cache;
|
|
33
|
+
}
|
|
34
|
+
async isRateLimited(key) {
|
|
35
|
+
if (this.cache) {
|
|
36
|
+
const attempts = await this.cache.get(`rl:${key}`);
|
|
37
|
+
return (attempts ?? 0) >= 5;
|
|
38
|
+
}
|
|
39
|
+
const record = this.failedAttempts.get(key);
|
|
40
|
+
if (!record)
|
|
41
|
+
return false;
|
|
42
|
+
if (Date.now() > record.expiresAt) {
|
|
43
|
+
this.failedAttempts.delete(key);
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return record.count >= 5;
|
|
47
|
+
}
|
|
48
|
+
async recordFailedAttempt(key) {
|
|
49
|
+
if (this.cache) {
|
|
50
|
+
const attempts = (await this.cache.get(`rl:${key}`)) ?? 0;
|
|
51
|
+
await this.cache.set(`rl:${key}`, attempts + 1, 15 * 60);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const record = this.failedAttempts.get(key);
|
|
55
|
+
if (!record || Date.now() > record.expiresAt) {
|
|
56
|
+
this.failedAttempts.set(key, {
|
|
57
|
+
count: 1,
|
|
58
|
+
expiresAt: Date.now() + 15 * 60 * 1000,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
record.count++;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async clearFailedAttempts(key) {
|
|
66
|
+
if (this.cache) {
|
|
67
|
+
await this.cache.delete(`rl:${key}`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
this.failedAttempts.delete(key);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Sign In Logic
|
|
74
|
+
*/
|
|
75
|
+
async signIn(data) {
|
|
76
|
+
const { email, password } = await loginSchema.parseAsync(data);
|
|
77
|
+
const normalizedEmail = email.toLowerCase().trim();
|
|
78
|
+
// Check rate limits for email and IP
|
|
79
|
+
const ip = await this.sessionService.getIPAddress();
|
|
80
|
+
const isEmailBlocked = await this.isRateLimited(`login:email:${normalizedEmail}`);
|
|
81
|
+
const isIpBlocked = ip ? await this.isRateLimited(`login:ip:${ip}`) : false;
|
|
82
|
+
if (isEmailBlocked || isIpBlocked) {
|
|
83
|
+
return {
|
|
84
|
+
status: "ERROR",
|
|
85
|
+
message: "Too many failed login attempts. Please try again in 15 minutes.",
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const user = await this.userService.getUserFromEmail(normalizedEmail);
|
|
89
|
+
if (!user) {
|
|
90
|
+
// Prevent timing attacks by running a dummy hash verification
|
|
91
|
+
await this.hasher.verify("$2b$10$abcdefghijklmnopqrstuuabcdefghijklmnopqrstuuabcdefghijk", password);
|
|
92
|
+
await this.recordFailedAttempt(`login:email:${normalizedEmail}`);
|
|
93
|
+
if (ip)
|
|
94
|
+
await this.recordFailedAttempt(`login:ip:${ip}`);
|
|
95
|
+
return { status: "ERROR", message: "Invalid email or password" };
|
|
96
|
+
}
|
|
97
|
+
const passwordHash = await this.userService.getUserPasswordHash(user.id);
|
|
98
|
+
if (!passwordHash || !(await this.hasher.verify(passwordHash, password))) {
|
|
99
|
+
await this.recordFailedAttempt(`login:email:${normalizedEmail}`);
|
|
100
|
+
if (ip)
|
|
101
|
+
await this.recordFailedAttempt(`login:ip:${ip}`);
|
|
102
|
+
return { status: "ERROR", message: "Invalid email or password" };
|
|
103
|
+
}
|
|
104
|
+
// Login succeeded - clear failed attempts
|
|
105
|
+
await this.clearFailedAttempts(`login:email:${normalizedEmail}`);
|
|
106
|
+
if (ip)
|
|
107
|
+
await this.clearFailedAttempts(`login:ip:${ip}`);
|
|
108
|
+
// Interception Layer
|
|
109
|
+
for (const validator of authValidators) {
|
|
110
|
+
const interception = await validator(user.id);
|
|
111
|
+
if (interception)
|
|
112
|
+
return interception;
|
|
113
|
+
}
|
|
114
|
+
const sessionFlags = {};
|
|
115
|
+
const sessionToken = await this.sessionService.generateSessionToken();
|
|
116
|
+
const session = await this.sessionService.createSession(sessionToken, user.id, sessionFlags);
|
|
117
|
+
await this.sessionService.setSessionTokenCookie(sessionToken, session.expiresAt);
|
|
118
|
+
const fullUser = await performFullUserAugmentation(user);
|
|
119
|
+
await eventBus.publish("system:auth:login", {
|
|
120
|
+
session,
|
|
121
|
+
user: fullUser,
|
|
122
|
+
});
|
|
123
|
+
await eventBus.publish("system:auth:session-created", {
|
|
124
|
+
session,
|
|
125
|
+
user: fullUser,
|
|
126
|
+
});
|
|
127
|
+
return {
|
|
128
|
+
status: "SUCCESS",
|
|
129
|
+
session: { ...session },
|
|
130
|
+
user: { ...fullUser },
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Sign Up Logic
|
|
135
|
+
*/
|
|
136
|
+
async signUp(data) {
|
|
137
|
+
const { email, password, username } = await registerSchema.parseAsync(data);
|
|
138
|
+
const normalizedEmail = email.toLowerCase().trim();
|
|
139
|
+
const existingUser = await this.userService.getUserFromEmail(normalizedEmail);
|
|
140
|
+
if (existingUser) {
|
|
141
|
+
return {
|
|
142
|
+
status: "ERROR",
|
|
143
|
+
message: "Email address is already registered",
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const user = await this.userService.createUser(normalizedEmail, username, password);
|
|
147
|
+
const sessionFlags = {};
|
|
148
|
+
const sessionToken = await this.sessionService.generateSessionToken();
|
|
149
|
+
const session = await this.sessionService.createSession(sessionToken, user.id, sessionFlags);
|
|
150
|
+
await this.sessionService.setSessionTokenCookie(sessionToken, session.expiresAt);
|
|
151
|
+
await this.emailVerificationService.createEmailVerificationRequest(user.id, user.email);
|
|
152
|
+
const fullUser = await performFullUserAugmentation(user);
|
|
153
|
+
await eventBus.publish("system:auth:register", {
|
|
154
|
+
session,
|
|
155
|
+
user: fullUser,
|
|
156
|
+
});
|
|
157
|
+
await eventBus.publish("system:auth:session-created", {
|
|
158
|
+
session,
|
|
159
|
+
user: fullUser,
|
|
160
|
+
});
|
|
161
|
+
return {
|
|
162
|
+
status: "SUCCESS",
|
|
163
|
+
session: { ...session },
|
|
164
|
+
user: { ...fullUser },
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Finalize Login Logic (used by MFA/Interceptions)
|
|
169
|
+
*/
|
|
170
|
+
async finalizeLogin(userId, flags = {}) {
|
|
171
|
+
const user = await this.userService.getUserById(userId);
|
|
172
|
+
if (!user) {
|
|
173
|
+
return { status: "ERROR", message: "User not found" };
|
|
174
|
+
}
|
|
175
|
+
const sessionToken = await this.sessionService.generateSessionToken();
|
|
176
|
+
const session = await this.sessionService.createSession(sessionToken, user.id, flags);
|
|
177
|
+
await this.sessionService.setSessionTokenCookie(sessionToken, session.expiresAt);
|
|
178
|
+
const fullUser = await performFullUserAugmentation(user);
|
|
179
|
+
await eventBus.publish("system:auth:session-created", {
|
|
180
|
+
session,
|
|
181
|
+
user: fullUser,
|
|
182
|
+
});
|
|
183
|
+
return {
|
|
184
|
+
status: "SUCCESS",
|
|
185
|
+
session: { ...session },
|
|
186
|
+
user: { ...fullUser },
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Sign Out Logic
|
|
191
|
+
*/
|
|
192
|
+
async signOut() {
|
|
193
|
+
await this.sessionService.sessionSignOut();
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
AuthService = __decorate([
|
|
197
|
+
Injectable(),
|
|
198
|
+
__param(0, Inject(UserService)),
|
|
199
|
+
__param(1, Inject(SessionService)),
|
|
200
|
+
__param(2, Inject(EmailVerificationService)),
|
|
201
|
+
__param(3, Inject(PASSWORD_HASHER)),
|
|
202
|
+
__param(4, Inject(CACHE_PROVIDER)),
|
|
203
|
+
__metadata("design:paramtypes", [UserService,
|
|
204
|
+
SessionService,
|
|
205
|
+
EmailVerificationService, Object, Object])
|
|
206
|
+
], AuthService);
|
|
207
|
+
export { AuthService };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AuthSession, FullUser, Session, SessionFlags, UserSession } from "../common/index.js";
|
|
2
|
+
export type { AuthSession, FullUser, Session, SessionFlags, UserSession };
|
|
3
|
+
/** A session together with its owner, as loaded by `findWithUser`. */
|
|
4
|
+
export interface SessionWithUser {
|
|
5
|
+
session: Session;
|
|
6
|
+
user: Record<string, any>;
|
|
7
|
+
}
|
|
8
|
+
export interface CreateSessionRecord {
|
|
9
|
+
id: string;
|
|
10
|
+
userId: string;
|
|
11
|
+
expiresAt: Date;
|
|
12
|
+
metadata?: Record<string, any>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Session persistence port.
|
|
16
|
+
*/
|
|
17
|
+
export interface ISessionRepository {
|
|
18
|
+
create(record: CreateSessionRecord): Promise<Session>;
|
|
19
|
+
/**
|
|
20
|
+
* Returns the session with its owner eager-loaded.
|
|
21
|
+
* Returns `null` when either the session or its owner is missing.
|
|
22
|
+
*/
|
|
23
|
+
findWithUser(id: string): Promise<SessionWithUser | null>;
|
|
24
|
+
findById(id: string): Promise<Session | null>;
|
|
25
|
+
findByUserId(userId: string): Promise<Session[]>;
|
|
26
|
+
updateMetadata(id: string, metadata: Record<string, any>): Promise<void>;
|
|
27
|
+
remove(id: string): Promise<void>;
|
|
28
|
+
removeByUserId(userId: string): Promise<void>;
|
|
29
|
+
/** Removes every session of a user except `currentSessionId`. */
|
|
30
|
+
removeOtherUserSessions(userId: string, currentSessionId: string): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
/** Injection token for the session repository port. */
|
|
33
|
+
export declare const SESSION_REPOSITORY: unique symbol;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AuthSession, SessionFlags, Session as SessionType, UserSession } from "../common/index.js";
|
|
2
|
+
export declare function getIPAddress(): Promise<string | null>;
|
|
3
|
+
export declare function validateSessionToken(token: string): Promise<AuthSession>;
|
|
4
|
+
export declare const getCurrentSession: () => Promise<AuthSession>;
|
|
5
|
+
export declare function invalidateSession(sessionId: string): Promise<void>;
|
|
6
|
+
export declare function invalidateUserSessions(userId: string): Promise<void>;
|
|
7
|
+
export declare function setSessionTokenCookie(token: string, expiresAt: Date): Promise<void>;
|
|
8
|
+
export declare function deleteSessionTokenCookie(): Promise<void>;
|
|
9
|
+
export declare function generateSessionToken(): Promise<string>;
|
|
10
|
+
export declare function createSession(token: string, userId: string, flags: SessionFlags): Promise<SessionType>;
|
|
11
|
+
export declare function updateSessionMetadata(flags: SessionFlags): Promise<void>;
|
|
12
|
+
export declare function sessionSignOut(): Promise<void>;
|
|
13
|
+
export declare function getUserSessions(userId: string, currentSessionId: string): Promise<UserSession[]>;
|
|
14
|
+
export declare function invalidateOtherSessions(userId: string, currentSessionId: string): Promise<void>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use server";
|
|
2
|
+
import { app } from "@veap/kernel";
|
|
3
|
+
import { cache } from "react";
|
|
4
|
+
import { authContext, isAuthContextBound } from "../common/context.js";
|
|
5
|
+
import { SessionService } from "./session.service.js";
|
|
6
|
+
async function resolveSessionService() {
|
|
7
|
+
const g = globalThis;
|
|
8
|
+
if (g.__VEAP_BOOTSTRAPPING_PROMISE__) {
|
|
9
|
+
try {
|
|
10
|
+
await g.__VEAP_BOOTSTRAPPING_PROMISE__;
|
|
11
|
+
}
|
|
12
|
+
catch { }
|
|
13
|
+
}
|
|
14
|
+
if (isAuthContextBound()) {
|
|
15
|
+
return authContext().session;
|
|
16
|
+
}
|
|
17
|
+
return await app(SessionService);
|
|
18
|
+
}
|
|
19
|
+
export async function getIPAddress() {
|
|
20
|
+
const service = await resolveSessionService();
|
|
21
|
+
return service.getIPAddress();
|
|
22
|
+
}
|
|
23
|
+
export async function validateSessionToken(token) {
|
|
24
|
+
const service = await resolveSessionService();
|
|
25
|
+
return service.validateSessionToken(token);
|
|
26
|
+
}
|
|
27
|
+
export const getCurrentSession = cache(async () => {
|
|
28
|
+
try {
|
|
29
|
+
const service = await resolveSessionService();
|
|
30
|
+
return await service.getCurrentSession();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return { session: null, user: null };
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
export async function invalidateSession(sessionId) {
|
|
37
|
+
const service = await resolveSessionService();
|
|
38
|
+
return service.invalidateSession(sessionId);
|
|
39
|
+
}
|
|
40
|
+
export async function invalidateUserSessions(userId) {
|
|
41
|
+
const service = await resolveSessionService();
|
|
42
|
+
return service.invalidateUserSessions(userId);
|
|
43
|
+
}
|
|
44
|
+
export async function setSessionTokenCookie(token, expiresAt) {
|
|
45
|
+
const service = await resolveSessionService();
|
|
46
|
+
return service.setSessionTokenCookie(token, expiresAt);
|
|
47
|
+
}
|
|
48
|
+
export async function deleteSessionTokenCookie() {
|
|
49
|
+
const service = await resolveSessionService();
|
|
50
|
+
return service.deleteSessionTokenCookie();
|
|
51
|
+
}
|
|
52
|
+
export async function generateSessionToken() {
|
|
53
|
+
const service = await resolveSessionService();
|
|
54
|
+
return service.generateSessionToken();
|
|
55
|
+
}
|
|
56
|
+
export async function createSession(token, userId, flags) {
|
|
57
|
+
const service = await resolveSessionService();
|
|
58
|
+
return service.createSession(token, userId, flags);
|
|
59
|
+
}
|
|
60
|
+
export async function updateSessionMetadata(flags) {
|
|
61
|
+
const service = await resolveSessionService();
|
|
62
|
+
return service.updateSessionMetadata(flags);
|
|
63
|
+
}
|
|
64
|
+
export async function sessionSignOut() {
|
|
65
|
+
const service = await resolveSessionService();
|
|
66
|
+
return service.sessionSignOut();
|
|
67
|
+
}
|
|
68
|
+
export async function getUserSessions(userId, currentSessionId) {
|
|
69
|
+
const service = await resolveSessionService();
|
|
70
|
+
return service.getUserSessions(userId, currentSessionId);
|
|
71
|
+
}
|
|
72
|
+
export async function invalidateOtherSessions(userId, currentSessionId) {
|
|
73
|
+
const service = await resolveSessionService();
|
|
74
|
+
return service.invalidateOtherSessions(userId, currentSessionId);
|
|
75
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Session as SessionEntity } from "../common/index.js";
|
|
2
|
+
import type { CreateSessionRecord, ISessionRepository, SessionWithUser } from "./contracts.js";
|
|
3
|
+
/**
|
|
4
|
+
* `ISessionRepository` adapter backed by the `Session` ActiveRecord model.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ActiveRecordSessionRepository implements ISessionRepository {
|
|
7
|
+
create(record: CreateSessionRecord): Promise<SessionEntity>;
|
|
8
|
+
findWithUser(id: string): Promise<SessionWithUser | null>;
|
|
9
|
+
findById(id: string): Promise<SessionEntity | null>;
|
|
10
|
+
findByUserId(userId: string): Promise<SessionEntity[]>;
|
|
11
|
+
updateMetadata(id: string, metadata: Record<string, any>): Promise<void>;
|
|
12
|
+
remove(id: string): Promise<void>;
|
|
13
|
+
removeByUserId(userId: string): Promise<void>;
|
|
14
|
+
removeOtherUserSessions(userId: string, currentSessionId: string): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Session } from "../models/Session.js";
|
|
2
|
+
/**
|
|
3
|
+
* `ISessionRepository` adapter backed by the `Session` ActiveRecord model.
|
|
4
|
+
*/
|
|
5
|
+
export class ActiveRecordSessionRepository {
|
|
6
|
+
async create(record) {
|
|
7
|
+
const session = await Session.create({
|
|
8
|
+
id: record.id,
|
|
9
|
+
expiresAt: record.expiresAt,
|
|
10
|
+
userId: record.userId,
|
|
11
|
+
metadata: record.metadata ?? {},
|
|
12
|
+
});
|
|
13
|
+
return session.toJSON();
|
|
14
|
+
}
|
|
15
|
+
async findWithUser(id) {
|
|
16
|
+
const session = await Session.query().with("user").find(id);
|
|
17
|
+
if (!session)
|
|
18
|
+
return null;
|
|
19
|
+
const user = session.getRelation("user") || session.user;
|
|
20
|
+
if (!user)
|
|
21
|
+
return null;
|
|
22
|
+
return {
|
|
23
|
+
session: session.toJSON(),
|
|
24
|
+
user: (typeof user.toJSON === "function"
|
|
25
|
+
? user.toJSON()
|
|
26
|
+
: user),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
async findById(id) {
|
|
30
|
+
const session = await Session.find(id);
|
|
31
|
+
return session ? session.toJSON() : null;
|
|
32
|
+
}
|
|
33
|
+
async findByUserId(userId) {
|
|
34
|
+
const sessions = await Session.where("user_id", userId).get();
|
|
35
|
+
return sessions.map((session) => session.toJSON());
|
|
36
|
+
}
|
|
37
|
+
async updateMetadata(id, metadata) {
|
|
38
|
+
await Session.where("id", id).update({
|
|
39
|
+
metadata,
|
|
40
|
+
updatedAt: new Date(),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async remove(id) {
|
|
44
|
+
await Session.destroy(id);
|
|
45
|
+
}
|
|
46
|
+
async removeByUserId(userId) {
|
|
47
|
+
await Session.where("user_id", userId).delete();
|
|
48
|
+
}
|
|
49
|
+
async removeOtherUserSessions(userId, currentSessionId) {
|
|
50
|
+
await Session.where("user_id", userId)
|
|
51
|
+
.toKnex()
|
|
52
|
+
.where("id", "!=", currentSessionId)
|
|
53
|
+
.delete();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type ICookieStore, type IHttpRequestContext } from "@veap/kernel";
|
|
2
|
+
import type { AuthSession, SessionFlags, Session as SessionType, UserSession } from "../common/index.js";
|
|
3
|
+
import { type ITokenGenerator } from "../crypto/contracts.js";
|
|
4
|
+
import { type ISessionRepository } from "./contracts.js";
|
|
5
|
+
export declare class SessionService {
|
|
6
|
+
private readonly sessions;
|
|
7
|
+
private readonly tokens;
|
|
8
|
+
private readonly cookieStore;
|
|
9
|
+
private readonly requestContext;
|
|
10
|
+
constructor(sessions: ISessionRepository, tokens: ITokenGenerator, cookieStore: ICookieStore, requestContext: IHttpRequestContext);
|
|
11
|
+
/**
|
|
12
|
+
* Returns the user's validated IP address, checking trusted proxy headers first.
|
|
13
|
+
*/
|
|
14
|
+
getIPAddress(): Promise<string | null>;
|
|
15
|
+
/**
|
|
16
|
+
* Validates the session token.
|
|
17
|
+
*/
|
|
18
|
+
validateSessionToken(token: string): Promise<AuthSession>;
|
|
19
|
+
/**
|
|
20
|
+
* Returns the current user session from cookies.
|
|
21
|
+
*/
|
|
22
|
+
getCurrentSession(): Promise<AuthSession>;
|
|
23
|
+
/**
|
|
24
|
+
* Invalidates a single session.
|
|
25
|
+
*/
|
|
26
|
+
invalidateSession(sessionId: string): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Invalidates all user sessions.
|
|
29
|
+
*/
|
|
30
|
+
invalidateUserSessions(userId: string): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Sets the session token in a cookie.
|
|
33
|
+
*/
|
|
34
|
+
setSessionTokenCookie(token: string, expiresAt: Date): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Removes the session token cookie.
|
|
37
|
+
*/
|
|
38
|
+
deleteSessionTokenCookie(): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Generates a new random session token.
|
|
41
|
+
*/
|
|
42
|
+
generateSessionToken(): Promise<string>;
|
|
43
|
+
/**
|
|
44
|
+
* Creates a new session in the database.
|
|
45
|
+
*/
|
|
46
|
+
createSession(token: string, userId: string, flags: SessionFlags): Promise<SessionType>;
|
|
47
|
+
/**
|
|
48
|
+
* Updates the current session metadata.
|
|
49
|
+
*/
|
|
50
|
+
updateSessionMetadata(flags: SessionFlags): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Signs the user out and redirects to the sign-in page.
|
|
53
|
+
*/
|
|
54
|
+
sessionSignOut(): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Get all active sessions for a user.
|
|
57
|
+
*/
|
|
58
|
+
getUserSessions(userId: string, currentSessionId: string): Promise<UserSession[]>;
|
|
59
|
+
/**
|
|
60
|
+
* Invalidate all sessions for a user except the specified current one.
|
|
61
|
+
*/
|
|
62
|
+
invalidateOtherSessions(userId: string, currentSessionId: string): Promise<void>;
|
|
63
|
+
}
|