@open-core/identity 1.0.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -658
- package/dist/contracts.d.ts +93 -0
- package/dist/contracts.js +21 -0
- package/dist/entities/account.entity.js +1 -2
- package/dist/entities/role.entity.js +1 -2
- package/dist/events/identity.events.js +1 -2
- package/dist/index.d.ts +86 -67
- package/dist/index.js +110 -99
- package/dist/providers/auth/api-auth.provider.d.ts +52 -0
- package/dist/providers/auth/api-auth.provider.js +82 -0
- package/dist/providers/auth/credentials-auth.provider.d.ts +63 -0
- package/dist/providers/auth/credentials-auth.provider.js +149 -0
- package/dist/providers/auth/local-auth.provider.d.ts +82 -0
- package/dist/providers/auth/local-auth.provider.js +151 -0
- package/dist/providers/identity-auth.provider.d.ts +0 -0
- package/dist/providers/identity-auth.provider.js +1 -0
- package/dist/providers/principal/api-principal.provider.d.ts +50 -0
- package/dist/providers/principal/api-principal.provider.js +84 -0
- package/dist/providers/principal/local-principal.provider.d.ts +77 -0
- package/dist/providers/principal/local-principal.provider.js +164 -0
- package/dist/repositories/account.repository.d.ts +4 -4
- package/dist/repositories/account.repository.js +2 -6
- package/dist/repositories/role.repository.d.ts +4 -4
- package/dist/repositories/role.repository.js +2 -6
- package/dist/services/account.service.d.ts +52 -57
- package/dist/services/account.service.js +80 -166
- package/dist/services/auth/api-auth.provider.js +7 -10
- package/dist/services/auth/credentials-auth.provider.js +8 -44
- package/dist/services/auth/local-auth.provider.js +7 -10
- package/dist/services/cache/memory-cache.service.js +4 -7
- package/dist/services/identity-auth.provider.js +7 -10
- package/dist/services/identity-principal.provider.js +12 -15
- package/dist/services/principal/api-principal.provider.js +9 -12
- package/dist/services/principal/local-principal.provider.js +12 -15
- package/dist/services/role.service.d.ts +33 -54
- package/dist/services/role.service.js +51 -109
- package/dist/setup.js +25 -28
- package/dist/tokens.d.ts +7 -0
- package/dist/tokens.js +7 -0
- package/dist/types/auth.types.js +1 -2
- package/dist/types/index.js +1 -2
- package/dist/types.d.ts +170 -0
- package/dist/types.js +1 -0
- package/package.json +13 -8
- package/migrations/001_accounts_table.sql +0 -16
- package/migrations/002_roles_table.sql +0 -21
- package/migrations/003_alter_accounts_add_role.sql +0 -24
- package/migrations/004_rename_uuid_to_linked_id.sql +0 -12
- package/migrations/005_add_password_hash.sql +0 -7
|
@@ -0,0 +1,164 @@
|
|
|
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 { injectable, inject } from "tsyringe";
|
|
14
|
+
import { Server } from "@open-core/framework";
|
|
15
|
+
import { IDENTITY_OPTIONS } from "../../tokens";
|
|
16
|
+
import { IdentityStore, RoleStore } from "../../contracts";
|
|
17
|
+
/**
|
|
18
|
+
* Authorization provider implementation for the OpenCore Framework.
|
|
19
|
+
*
|
|
20
|
+
* This provider resolves player principals (roles and permissions) by
|
|
21
|
+
* interacting with the configured {@link IdentityStore} and {@link RoleStore}.
|
|
22
|
+
* It includes a high-performance in-memory cache to minimize database
|
|
23
|
+
* overhead during frequent security checks (e.g., in `@Guard` decorators).
|
|
24
|
+
*
|
|
25
|
+
* @injectable
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
let IdentityPrincipalProvider = class IdentityPrincipalProvider extends Server.PrincipalProviderContract {
|
|
29
|
+
/**
|
|
30
|
+
* Initializes a new instance of the IdentityPrincipalProvider.
|
|
31
|
+
*
|
|
32
|
+
* @param options - Identity system configuration options.
|
|
33
|
+
* @param accountStore - Persistence layer for account data.
|
|
34
|
+
* @param roleStore - Optional persistence layer for dynamic roles.
|
|
35
|
+
*/
|
|
36
|
+
constructor(options, accountStore, roleStore) {
|
|
37
|
+
super();
|
|
38
|
+
this.options = options;
|
|
39
|
+
this.accountStore = accountStore;
|
|
40
|
+
this.roleStore = roleStore;
|
|
41
|
+
/**
|
|
42
|
+
* In-memory cache for resolved principals.
|
|
43
|
+
* Key: clientId (number)
|
|
44
|
+
*/
|
|
45
|
+
this.cache = new Map();
|
|
46
|
+
this.cacheTtl = options.principal.cacheTtl ?? 300000; // 5 minutes default
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Resolves the security Principal for a connected player.
|
|
50
|
+
*
|
|
51
|
+
* This method first checks the internal cache. If missing or expired,
|
|
52
|
+
* it resolves the account and its effective permissions.
|
|
53
|
+
*
|
|
54
|
+
* @param player - The framework player entity.
|
|
55
|
+
* @returns A promise resolving to the {@link Server.Principal} or null if not authenticated.
|
|
56
|
+
*/
|
|
57
|
+
async getPrincipal(player) {
|
|
58
|
+
const clientId = player.clientID;
|
|
59
|
+
const cached = this.cache.get(clientId);
|
|
60
|
+
if (cached && cached.expiresAt > Date.now()) {
|
|
61
|
+
return cached.principal;
|
|
62
|
+
}
|
|
63
|
+
const linkedId = player.accountID;
|
|
64
|
+
if (!linkedId)
|
|
65
|
+
return null;
|
|
66
|
+
const principal = await this.resolvePrincipal(linkedId);
|
|
67
|
+
if (principal) {
|
|
68
|
+
this.cache.set(clientId, {
|
|
69
|
+
principal,
|
|
70
|
+
expiresAt: Date.now() + this.cacheTtl,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return principal;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Invalidates the cache and re-resolves the principal for a player.
|
|
77
|
+
*
|
|
78
|
+
* @param player - The player whose principal should be refreshed.
|
|
79
|
+
*/
|
|
80
|
+
async refreshPrincipal(player) {
|
|
81
|
+
this.cache.delete(player.clientID);
|
|
82
|
+
await this.getPrincipal(player);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Resolves a principal for offline workflows using a stable account ID.
|
|
86
|
+
*
|
|
87
|
+
* @param linkedID - The linked account identifier.
|
|
88
|
+
* @returns A promise resolving to the principal or null.
|
|
89
|
+
*/
|
|
90
|
+
async getPrincipalByLinkedID(linkedID) {
|
|
91
|
+
return this.resolvePrincipal(linkedID);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Internal logic to resolve effective permissions and construct the Principal.
|
|
95
|
+
*
|
|
96
|
+
* @param linkedId - The stable account ID.
|
|
97
|
+
* @returns Resolves the role, merges permissions, and returns the Principal.
|
|
98
|
+
* @internal
|
|
99
|
+
*/
|
|
100
|
+
async resolvePrincipal(linkedId) {
|
|
101
|
+
const account = await this.accountStore.findByLinkedId(linkedId);
|
|
102
|
+
if (!account)
|
|
103
|
+
return null;
|
|
104
|
+
let role;
|
|
105
|
+
if (this.options.principal.mode === "roles") {
|
|
106
|
+
role = this.options.principal.roles?.[account.roleName];
|
|
107
|
+
}
|
|
108
|
+
else if (this.roleStore) {
|
|
109
|
+
const dbRole = await this.roleStore.findByName(account.roleName);
|
|
110
|
+
if (dbRole)
|
|
111
|
+
role = dbRole;
|
|
112
|
+
}
|
|
113
|
+
if (!role) {
|
|
114
|
+
const defaultName = this.options.principal.defaultRole || "user";
|
|
115
|
+
role = this.options.principal.roles?.[defaultName];
|
|
116
|
+
}
|
|
117
|
+
if (!role)
|
|
118
|
+
return null;
|
|
119
|
+
const effectivePermissions = this.mergePermissions(role.permissions, account.customPermissions);
|
|
120
|
+
return {
|
|
121
|
+
id: account.linkedId,
|
|
122
|
+
name: role.displayName || role.name,
|
|
123
|
+
rank: role.rank,
|
|
124
|
+
permissions: effectivePermissions,
|
|
125
|
+
meta: {
|
|
126
|
+
accountId: account.id,
|
|
127
|
+
roleName: role.name,
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Merges role-based permissions with account-specific overrides.
|
|
133
|
+
*
|
|
134
|
+
* Overrides starting with '-' are removed, and those starting with '+'
|
|
135
|
+
* (or without prefix) are added to the final set.
|
|
136
|
+
*
|
|
137
|
+
* @param base - Base permissions from the role.
|
|
138
|
+
* @param overrides - Custom overrides from the account.
|
|
139
|
+
* @returns The unified list of effective permissions.
|
|
140
|
+
* @internal
|
|
141
|
+
*/
|
|
142
|
+
mergePermissions(base, overrides) {
|
|
143
|
+
const perms = new Set(base);
|
|
144
|
+
for (const override of overrides) {
|
|
145
|
+
if (override.startsWith("-")) {
|
|
146
|
+
perms.delete(override.substring(1));
|
|
147
|
+
}
|
|
148
|
+
else if (override.startsWith("+")) {
|
|
149
|
+
perms.add(override.substring(1));
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
perms.add(override);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return Array.from(perms);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
IdentityPrincipalProvider = __decorate([
|
|
159
|
+
injectable(),
|
|
160
|
+
__param(0, inject(IDENTITY_OPTIONS)),
|
|
161
|
+
__metadata("design:paramtypes", [Object, IdentityStore,
|
|
162
|
+
RoleStore])
|
|
163
|
+
], IdentityPrincipalProvider);
|
|
164
|
+
export { IdentityPrincipalProvider };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Repository, type DatabaseContract } from "@open-core/framework/server";
|
|
2
2
|
import type { Account } from "../entities/account.entity";
|
|
3
3
|
import type { Role } from "../entities/role.entity";
|
|
4
4
|
import type { CreateAccountInput, IdentifierType } from "../types";
|
|
@@ -21,10 +21,10 @@ interface AccountRow {
|
|
|
21
21
|
/**
|
|
22
22
|
* Repository for the accounts table.
|
|
23
23
|
*/
|
|
24
|
-
export declare class AccountRepository extends
|
|
25
|
-
protected readonly db:
|
|
24
|
+
export declare class AccountRepository extends Repository<Account> {
|
|
25
|
+
protected readonly db: DatabaseContract;
|
|
26
26
|
protected tableName: string;
|
|
27
|
-
constructor(db:
|
|
27
|
+
constructor(db: DatabaseContract);
|
|
28
28
|
findByLinkedId(linkedId: string): Promise<Account | null>;
|
|
29
29
|
findByIdentifier(type: IdentifierType, value: string): Promise<Account | null>;
|
|
30
30
|
createAccount(input: CreateAccountInput): Promise<Account>;
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AccountRepository = void 0;
|
|
4
|
-
const framework_1 = require("@open-core/framework");
|
|
1
|
+
import { Repository } from "@open-core/framework/server";
|
|
5
2
|
/**
|
|
6
3
|
* Repository for the accounts table.
|
|
7
4
|
*/
|
|
8
|
-
class AccountRepository extends
|
|
5
|
+
export class AccountRepository extends Repository {
|
|
9
6
|
constructor(db) {
|
|
10
7
|
super(db);
|
|
11
8
|
this.db = db;
|
|
@@ -182,4 +179,3 @@ class AccountRepository extends framework_1.Server.Repository {
|
|
|
182
179
|
}
|
|
183
180
|
}
|
|
184
181
|
}
|
|
185
|
-
exports.AccountRepository = AccountRepository;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Repository, type DatabaseContract } from "@open-core/framework/server";
|
|
2
2
|
import type { Role } from "../entities/role.entity";
|
|
3
3
|
interface RoleRow {
|
|
4
4
|
id?: number;
|
|
@@ -13,10 +13,10 @@ interface RoleRow {
|
|
|
13
13
|
* Repository for the roles table.
|
|
14
14
|
* Manages CRUD operations for roles with custom queries for defaults and names.
|
|
15
15
|
*/
|
|
16
|
-
export declare class RoleRepository extends
|
|
17
|
-
protected readonly db:
|
|
16
|
+
export declare class RoleRepository extends Repository<Role> {
|
|
17
|
+
protected readonly db: DatabaseContract;
|
|
18
18
|
protected tableName: string;
|
|
19
|
-
constructor(db:
|
|
19
|
+
constructor(db: DatabaseContract);
|
|
20
20
|
/**
|
|
21
21
|
* Find a role by its internal name.
|
|
22
22
|
*
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RoleRepository = void 0;
|
|
4
|
-
const framework_1 = require("@open-core/framework");
|
|
1
|
+
import { Repository } from "@open-core/framework/server";
|
|
5
2
|
/**
|
|
6
3
|
* Repository for the roles table.
|
|
7
4
|
* Manages CRUD operations for roles with custom queries for defaults and names.
|
|
8
5
|
*/
|
|
9
|
-
class RoleRepository extends
|
|
6
|
+
export class RoleRepository extends Repository {
|
|
10
7
|
constructor(db) {
|
|
11
8
|
super(db);
|
|
12
9
|
this.db = db;
|
|
@@ -76,4 +73,3 @@ class RoleRepository extends framework_1.Server.Repository {
|
|
|
76
73
|
};
|
|
77
74
|
}
|
|
78
75
|
}
|
|
79
|
-
exports.RoleRepository = RoleRepository;
|
|
@@ -1,78 +1,73 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { IdentityStore } from "../contracts";
|
|
2
|
+
import type { IdentityAccount, IdentityOptions } from "../types";
|
|
3
|
+
/**
|
|
4
|
+
* High-level service for managing identity accounts and security policies.
|
|
5
|
+
*
|
|
6
|
+
* Provides a programmer-friendly API for account administration, including
|
|
7
|
+
* role assignment, permission overrides, and ban management.
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
* @injectable
|
|
11
|
+
*/
|
|
6
12
|
export declare class AccountService {
|
|
7
|
-
private readonly
|
|
8
|
-
private readonly
|
|
9
|
-
|
|
10
|
-
constructor(repo: AccountRepository, roleService: RoleService, config: Server.ConfigService);
|
|
11
|
-
findById(id: number): Promise<Account | null>;
|
|
12
|
-
findByLinkedId(linkedId: string): Promise<Account | null>;
|
|
13
|
-
findByIdentifier(type: IdentifierType, value: string): Promise<Account | null>;
|
|
14
|
-
findOrCreate(identifiers: AccountIdentifiers): Promise<{
|
|
15
|
-
account: Account;
|
|
16
|
-
isNew: boolean;
|
|
17
|
-
}>;
|
|
18
|
-
ban(accountId: number, options: BanOptions): Promise<void>;
|
|
19
|
-
unban(accountId: number): Promise<void>;
|
|
20
|
-
isBanExpired(account: Account): boolean;
|
|
13
|
+
private readonly store;
|
|
14
|
+
private readonly options;
|
|
15
|
+
constructor(store: IdentityStore, options: IdentityOptions);
|
|
21
16
|
/**
|
|
22
|
-
*
|
|
17
|
+
* Retrieves an account by its unique numeric or internal ID.
|
|
23
18
|
*
|
|
24
|
-
* @param
|
|
25
|
-
* @
|
|
19
|
+
* @param id - The internal account identifier.
|
|
20
|
+
* @returns A promise resolving to the account or null if not found.
|
|
26
21
|
*/
|
|
27
|
-
|
|
22
|
+
findById(id: string): Promise<IdentityAccount | null>;
|
|
28
23
|
/**
|
|
29
|
-
*
|
|
24
|
+
* Retrieves an account by its stable linked ID.
|
|
30
25
|
*
|
|
31
|
-
* @param
|
|
32
|
-
* @
|
|
26
|
+
* @param linkedId - The stable ID (UUID or external system ID).
|
|
27
|
+
* @returns A promise resolving to the account or null if not found.
|
|
33
28
|
*/
|
|
34
|
-
|
|
29
|
+
findByLinkedId(linkedId: string): Promise<IdentityAccount | null>;
|
|
35
30
|
/**
|
|
36
|
-
*
|
|
31
|
+
* Assigns a security role to an account.
|
|
37
32
|
*
|
|
38
|
-
* @param accountId -
|
|
39
|
-
* @
|
|
33
|
+
* @param accountId - The linked ID of the account.
|
|
34
|
+
* @param roleName - Technical name of the role to assign.
|
|
40
35
|
*/
|
|
41
|
-
|
|
36
|
+
assignRole(accountId: string, roleName: string): Promise<void>;
|
|
42
37
|
/**
|
|
43
|
-
*
|
|
38
|
+
* Grants a custom permission override to an account.
|
|
44
39
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*/
|
|
48
|
-
assignRole(accountId: number, roleId: number | null): Promise<void>;
|
|
49
|
-
/**
|
|
50
|
-
* Combine role permissions with account custom permissions.
|
|
51
|
-
* Custom permissions starting with '-' negate the base permission.
|
|
40
|
+
* This override takes precedence over role permissions.
|
|
41
|
+
* Use the `+` prefix for clarity (optional).
|
|
52
42
|
*
|
|
53
|
-
* @param
|
|
54
|
-
* @param
|
|
55
|
-
* @returns Combined permissions array
|
|
43
|
+
* @param accountId - The linked ID of the account.
|
|
44
|
+
* @param permission - The permission string to grant.
|
|
56
45
|
*/
|
|
57
|
-
|
|
58
|
-
touchLastLogin(accountId: number, date?: Date): Promise<void>;
|
|
59
|
-
private lookupExisting;
|
|
60
|
-
private identifierPriority;
|
|
46
|
+
addCustomPermission(accountId: string, permission: string): Promise<void>;
|
|
61
47
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
48
|
+
* Revokes a custom permission override.
|
|
49
|
+
*
|
|
50
|
+
* To explicitly deny a permission that a role might grant, use the `-` prefix
|
|
51
|
+
* (e.g., `-chat.use`).
|
|
52
|
+
*
|
|
53
|
+
* @param accountId - The linked ID of the account.
|
|
54
|
+
* @param permission - The permission string to remove or revoke.
|
|
64
55
|
*/
|
|
65
|
-
|
|
56
|
+
removeCustomPermission(accountId: string, permission: string): Promise<void>;
|
|
66
57
|
/**
|
|
67
|
-
*
|
|
58
|
+
* Prohibits an account from connecting to the server.
|
|
59
|
+
*
|
|
60
|
+
* @param accountId - The linked ID of the account.
|
|
61
|
+
* @param options - Ban details including optional reason and duration.
|
|
68
62
|
*/
|
|
69
|
-
|
|
63
|
+
ban(accountId: string, options?: {
|
|
64
|
+
reason?: string;
|
|
65
|
+
durationMs?: number;
|
|
66
|
+
}): Promise<void>;
|
|
70
67
|
/**
|
|
71
|
-
*
|
|
68
|
+
* Lifts an active ban from an account.
|
|
69
|
+
*
|
|
70
|
+
* @param accountId - The linked ID of the account.
|
|
72
71
|
*/
|
|
73
|
-
|
|
74
|
-
username: string;
|
|
75
|
-
passwordHash: string;
|
|
76
|
-
identifiers: AccountIdentifiers;
|
|
77
|
-
}): Promise<Account>;
|
|
72
|
+
unban(accountId: string): Promise<void>;
|
|
78
73
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
2
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
3
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -8,200 +7,115 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
8
7
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
8
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
9
|
};
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
import { injectable, inject } from "tsyringe";
|
|
14
|
+
import { IDENTITY_OPTIONS } from "../tokens";
|
|
15
|
+
import { IdentityStore } from "../contracts";
|
|
16
|
+
/**
|
|
17
|
+
* High-level service for managing identity accounts and security policies.
|
|
18
|
+
*
|
|
19
|
+
* Provides a programmer-friendly API for account administration, including
|
|
20
|
+
* role assignment, permission overrides, and ban management.
|
|
21
|
+
*
|
|
22
|
+
* @public
|
|
23
|
+
* @injectable
|
|
24
|
+
*/
|
|
18
25
|
let AccountService = class AccountService {
|
|
19
|
-
constructor(
|
|
20
|
-
this.
|
|
21
|
-
this.
|
|
22
|
-
this.config = config;
|
|
26
|
+
constructor(store, options) {
|
|
27
|
+
this.store = store;
|
|
28
|
+
this.options = options;
|
|
23
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Retrieves an account by its unique numeric or internal ID.
|
|
32
|
+
*
|
|
33
|
+
* @param id - The internal account identifier.
|
|
34
|
+
* @returns A promise resolving to the account or null if not found.
|
|
35
|
+
*/
|
|
24
36
|
async findById(id) {
|
|
25
|
-
return this.
|
|
37
|
+
return this.store.findByLinkedId(id); // Using linkedId as the primary public handle
|
|
26
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Retrieves an account by its stable linked ID.
|
|
41
|
+
*
|
|
42
|
+
* @param linkedId - The stable ID (UUID or external system ID).
|
|
43
|
+
* @returns A promise resolving to the account or null if not found.
|
|
44
|
+
*/
|
|
27
45
|
async findByLinkedId(linkedId) {
|
|
28
|
-
return this.
|
|
29
|
-
}
|
|
30
|
-
async findByIdentifier(type, value) {
|
|
31
|
-
return this.repo.findByIdentifier(type, value);
|
|
46
|
+
return this.store.findByLinkedId(linkedId);
|
|
32
47
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const created = await this.repo.createAccount({
|
|
42
|
-
linkedId: (0, crypto_1.randomUUID)(),
|
|
43
|
-
externalSource: "local",
|
|
44
|
-
license: identifiers.license ?? null,
|
|
45
|
-
discord: identifiers.discord ?? null,
|
|
46
|
-
steam: identifiers.steam ?? null,
|
|
47
|
-
username: null,
|
|
48
|
-
roleId: defaultRole?.id ?? null,
|
|
49
|
-
});
|
|
50
|
-
return { account: created, isNew: true };
|
|
51
|
-
}
|
|
52
|
-
async ban(accountId, options) {
|
|
53
|
-
const expires = options.durationMs
|
|
54
|
-
? new Date(Date.now() + options.durationMs)
|
|
55
|
-
: null;
|
|
56
|
-
await this.repo.setBan(accountId, true, options.reason ?? "Banned", expires);
|
|
57
|
-
}
|
|
58
|
-
async unban(accountId) {
|
|
59
|
-
await this.repo.setBan(accountId, false, null, null);
|
|
60
|
-
}
|
|
61
|
-
isBanExpired(account) {
|
|
62
|
-
if (!account.banned)
|
|
63
|
-
return false;
|
|
64
|
-
if (!account.banExpires)
|
|
65
|
-
return false;
|
|
66
|
-
return account.banExpires.getTime() <= Date.now();
|
|
48
|
+
/**
|
|
49
|
+
* Assigns a security role to an account.
|
|
50
|
+
*
|
|
51
|
+
* @param accountId - The linked ID of the account.
|
|
52
|
+
* @param roleName - Technical name of the role to assign.
|
|
53
|
+
*/
|
|
54
|
+
async assignRole(accountId, roleName) {
|
|
55
|
+
await this.store.update(accountId, { roleName });
|
|
67
56
|
}
|
|
68
57
|
/**
|
|
69
|
-
*
|
|
58
|
+
* Grants a custom permission override to an account.
|
|
59
|
+
*
|
|
60
|
+
* This override takes precedence over role permissions.
|
|
61
|
+
* Use the `+` prefix for clarity (optional).
|
|
70
62
|
*
|
|
71
|
-
* @param accountId -
|
|
72
|
-
* @param permission -
|
|
63
|
+
* @param accountId - The linked ID of the account.
|
|
64
|
+
* @param permission - The permission string to grant.
|
|
73
65
|
*/
|
|
74
66
|
async addCustomPermission(accountId, permission) {
|
|
75
|
-
const account = await this.
|
|
67
|
+
const account = await this.store.findByLinkedId(accountId);
|
|
76
68
|
if (!account)
|
|
77
69
|
return;
|
|
78
|
-
const permissions = new Set(account.customPermissions
|
|
70
|
+
const permissions = new Set(account.customPermissions);
|
|
79
71
|
permissions.add(permission);
|
|
80
|
-
await this.
|
|
72
|
+
await this.store.update(accountId, {
|
|
73
|
+
customPermissions: Array.from(permissions),
|
|
74
|
+
});
|
|
81
75
|
}
|
|
82
76
|
/**
|
|
83
|
-
*
|
|
77
|
+
* Revokes a custom permission override.
|
|
78
|
+
*
|
|
79
|
+
* To explicitly deny a permission that a role might grant, use the `-` prefix
|
|
80
|
+
* (e.g., `-chat.use`).
|
|
84
81
|
*
|
|
85
|
-
* @param accountId -
|
|
86
|
-
* @param permission -
|
|
82
|
+
* @param accountId - The linked ID of the account.
|
|
83
|
+
* @param permission - The permission string to remove or revoke.
|
|
87
84
|
*/
|
|
88
85
|
async removeCustomPermission(accountId, permission) {
|
|
89
|
-
const account = await this.
|
|
86
|
+
const account = await this.store.findByLinkedId(accountId);
|
|
90
87
|
if (!account)
|
|
91
88
|
return;
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
*
|
|
98
|
-
* @param accountId - Account ID
|
|
99
|
-
* @returns Combined permissions array
|
|
100
|
-
*/
|
|
101
|
-
async getEffectivePermissions(accountId) {
|
|
102
|
-
const result = await this.repo.findByIdWithRole(accountId);
|
|
103
|
-
if (!result)
|
|
104
|
-
return [];
|
|
105
|
-
return this.combinePermissions(result.role, result.account.customPermissions);
|
|
89
|
+
const permissions = new Set(account.customPermissions);
|
|
90
|
+
permissions.delete(permission);
|
|
91
|
+
await this.store.update(accountId, {
|
|
92
|
+
customPermissions: Array.from(permissions),
|
|
93
|
+
});
|
|
106
94
|
}
|
|
107
95
|
/**
|
|
108
|
-
*
|
|
96
|
+
* Prohibits an account from connecting to the server.
|
|
109
97
|
*
|
|
110
|
-
* @param accountId -
|
|
111
|
-
* @param
|
|
98
|
+
* @param accountId - The linked ID of the account.
|
|
99
|
+
* @param options - Ban details including optional reason and duration.
|
|
112
100
|
*/
|
|
113
|
-
async
|
|
114
|
-
|
|
101
|
+
async ban(accountId, options = {}) {
|
|
102
|
+
const expiresAt = options.durationMs
|
|
103
|
+
? new Date(Date.now() + options.durationMs)
|
|
104
|
+
: null;
|
|
105
|
+
await this.store.setBan(accountId, true, options.reason, expiresAt);
|
|
115
106
|
}
|
|
116
107
|
/**
|
|
117
|
-
*
|
|
118
|
-
* Custom permissions starting with '-' negate the base permission.
|
|
108
|
+
* Lifts an active ban from an account.
|
|
119
109
|
*
|
|
120
|
-
* @param
|
|
121
|
-
* @param customPerms - Account custom permissions
|
|
122
|
-
* @returns Combined permissions array
|
|
110
|
+
* @param accountId - The linked ID of the account.
|
|
123
111
|
*/
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
for (const perm of customPerms) {
|
|
127
|
-
if (perm.startsWith("-")) {
|
|
128
|
-
// Negation: remove the base permission
|
|
129
|
-
base.delete(perm.slice(1));
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
// Addition: add custom permission
|
|
133
|
-
base.add(perm);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return Array.from(base);
|
|
137
|
-
}
|
|
138
|
-
async touchLastLogin(accountId, date = new Date()) {
|
|
139
|
-
await this.repo.updateLastLogin(accountId, date);
|
|
140
|
-
}
|
|
141
|
-
async lookupExisting(identifiers) {
|
|
142
|
-
const ordered = this.identifierPriority();
|
|
143
|
-
for (const key of ordered) {
|
|
144
|
-
const value = identifiers[key];
|
|
145
|
-
if (!value)
|
|
146
|
-
continue;
|
|
147
|
-
const found = await this.repo.findByIdentifier(key, value);
|
|
148
|
-
if (found)
|
|
149
|
-
return found;
|
|
150
|
-
}
|
|
151
|
-
// Fallback: try any provided identifiers
|
|
152
|
-
for (const [key, value] of Object.entries(identifiers)) {
|
|
153
|
-
if (!value)
|
|
154
|
-
continue;
|
|
155
|
-
const found = await this.repo.findByIdentifier(key, value);
|
|
156
|
-
if (found)
|
|
157
|
-
return found;
|
|
158
|
-
}
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
identifierPriority() {
|
|
162
|
-
const fromConfig = this.config.get("identity_primary_identifier", "license");
|
|
163
|
-
const base = ["license", "discord", "steam"];
|
|
164
|
-
return [fromConfig, ...base.filter((id) => id !== fromConfig)];
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Find account by username (for credentials auth)
|
|
168
|
-
* Note: This is a basic implementation. For production, add an index on username.
|
|
169
|
-
*/
|
|
170
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
171
|
-
async findByUsername(_username) {
|
|
172
|
-
// TODO: Add findByUsername method to AccountRepository for better performance
|
|
173
|
-
// For now, this is a placeholder that credentials auth will need
|
|
174
|
-
throw new Error("findByUsername not implemented - add to AccountRepository for credentials auth");
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Update account identifiers (for credentials auth identifier merging)
|
|
178
|
-
*/
|
|
179
|
-
async updateIdentifiers() {
|
|
180
|
-
// This is a placeholder - in production you'd want a proper update method
|
|
181
|
-
// For now, credentials auth can work without this
|
|
182
|
-
console.warn("updateIdentifiers not fully implemented - identifiers not merged");
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Create account with credentials (for CredentialsAuthProvider)
|
|
186
|
-
*/
|
|
187
|
-
async createWithCredentials(input) {
|
|
188
|
-
const defaultRole = await this.roleService.getDefaultRole();
|
|
189
|
-
// Note: This creates an account without password_hash field
|
|
190
|
-
// You'll need to add password_hash to Account entity and migration 005
|
|
191
|
-
return this.repo.createAccount({
|
|
192
|
-
linkedId: (0, crypto_1.randomUUID)(),
|
|
193
|
-
externalSource: "credentials",
|
|
194
|
-
username: input.username,
|
|
195
|
-
license: input.identifiers.license ?? null,
|
|
196
|
-
discord: input.identifiers.discord ?? null,
|
|
197
|
-
steam: input.identifiers.steam ?? null,
|
|
198
|
-
roleId: defaultRole?.id ?? null,
|
|
199
|
-
});
|
|
112
|
+
async unban(accountId) {
|
|
113
|
+
await this.store.setBan(accountId, false);
|
|
200
114
|
}
|
|
201
115
|
};
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
(
|
|
205
|
-
__metadata("design:paramtypes", [
|
|
206
|
-
role_service_1.RoleService, framework_1.Server.ConfigService])
|
|
116
|
+
AccountService = __decorate([
|
|
117
|
+
injectable(),
|
|
118
|
+
__param(1, inject(IDENTITY_OPTIONS)),
|
|
119
|
+
__metadata("design:paramtypes", [IdentityStore, Object])
|
|
207
120
|
], AccountService);
|
|
121
|
+
export { AccountService };
|