@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.
Files changed (49) hide show
  1. package/README.md +54 -658
  2. package/dist/contracts.d.ts +93 -0
  3. package/dist/contracts.js +21 -0
  4. package/dist/entities/account.entity.js +1 -2
  5. package/dist/entities/role.entity.js +1 -2
  6. package/dist/events/identity.events.js +1 -2
  7. package/dist/index.d.ts +86 -67
  8. package/dist/index.js +110 -99
  9. package/dist/providers/auth/api-auth.provider.d.ts +52 -0
  10. package/dist/providers/auth/api-auth.provider.js +82 -0
  11. package/dist/providers/auth/credentials-auth.provider.d.ts +63 -0
  12. package/dist/providers/auth/credentials-auth.provider.js +149 -0
  13. package/dist/providers/auth/local-auth.provider.d.ts +82 -0
  14. package/dist/providers/auth/local-auth.provider.js +151 -0
  15. package/dist/providers/identity-auth.provider.d.ts +0 -0
  16. package/dist/providers/identity-auth.provider.js +1 -0
  17. package/dist/providers/principal/api-principal.provider.d.ts +50 -0
  18. package/dist/providers/principal/api-principal.provider.js +84 -0
  19. package/dist/providers/principal/local-principal.provider.d.ts +77 -0
  20. package/dist/providers/principal/local-principal.provider.js +164 -0
  21. package/dist/repositories/account.repository.d.ts +4 -4
  22. package/dist/repositories/account.repository.js +2 -6
  23. package/dist/repositories/role.repository.d.ts +4 -4
  24. package/dist/repositories/role.repository.js +2 -6
  25. package/dist/services/account.service.d.ts +52 -57
  26. package/dist/services/account.service.js +80 -166
  27. package/dist/services/auth/api-auth.provider.js +7 -10
  28. package/dist/services/auth/credentials-auth.provider.js +8 -44
  29. package/dist/services/auth/local-auth.provider.js +7 -10
  30. package/dist/services/cache/memory-cache.service.js +4 -7
  31. package/dist/services/identity-auth.provider.js +7 -10
  32. package/dist/services/identity-principal.provider.js +12 -15
  33. package/dist/services/principal/api-principal.provider.js +9 -12
  34. package/dist/services/principal/local-principal.provider.js +12 -15
  35. package/dist/services/role.service.d.ts +33 -54
  36. package/dist/services/role.service.js +51 -109
  37. package/dist/setup.js +25 -28
  38. package/dist/tokens.d.ts +7 -0
  39. package/dist/tokens.js +7 -0
  40. package/dist/types/auth.types.js +1 -2
  41. package/dist/types/index.js +1 -2
  42. package/dist/types.d.ts +170 -0
  43. package/dist/types.js +1 -0
  44. package/package.json +13 -8
  45. package/migrations/001_accounts_table.sql +0 -16
  46. package/migrations/002_roles_table.sql +0 -21
  47. package/migrations/003_alter_accounts_add_role.sql +0 -24
  48. package/migrations/004_rename_uuid_to_linked_id.sql +0 -12
  49. 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 { Server } from "@open-core/framework";
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 Server.Repository<Account> {
25
- protected readonly db: Server.DatabaseContract;
24
+ export declare class AccountRepository extends Repository<Account> {
25
+ protected readonly db: DatabaseContract;
26
26
  protected tableName: string;
27
- constructor(db: Server.DatabaseContract);
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
- "use strict";
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 framework_1.Server.Repository {
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 { Server } from "@open-core/framework";
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 Server.Repository<Role> {
17
- protected readonly db: Server.DatabaseContract;
16
+ export declare class RoleRepository extends Repository<Role> {
17
+ protected readonly db: DatabaseContract;
18
18
  protected tableName: string;
19
- constructor(db: Server.DatabaseContract);
19
+ constructor(db: DatabaseContract);
20
20
  /**
21
21
  * Find a role by its internal name.
22
22
  *
@@ -1,12 +1,9 @@
1
- "use strict";
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 framework_1.Server.Repository {
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 { Server } from "@open-core/framework";
2
- import type { Account } from "../entities/account.entity";
3
- import type { AccountIdentifiers, BanOptions, IdentifierType } from "../types";
4
- import { AccountRepository } from "../repositories/account.repository";
5
- import { RoleService } from "./role.service";
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 repo;
8
- private readonly roleService;
9
- private readonly config;
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
- * Add a custom permission to an account (override/additional to role).
17
+ * Retrieves an account by its unique numeric or internal ID.
23
18
  *
24
- * @param accountId - Account ID
25
- * @param permission - Permission string to add
19
+ * @param id - The internal account identifier.
20
+ * @returns A promise resolving to the account or null if not found.
26
21
  */
27
- addCustomPermission(accountId: number, permission: string): Promise<void>;
22
+ findById(id: string): Promise<IdentityAccount | null>;
28
23
  /**
29
- * Remove a custom permission from an account.
24
+ * Retrieves an account by its stable linked ID.
30
25
  *
31
- * @param accountId - Account ID
32
- * @param permission - Permission string to remove
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
- removeCustomPermission(accountId: number, permission: string): Promise<void>;
29
+ findByLinkedId(linkedId: string): Promise<IdentityAccount | null>;
35
30
  /**
36
- * Get effective permissions for an account (role + custom).
31
+ * Assigns a security role to an account.
37
32
  *
38
- * @param accountId - Account ID
39
- * @returns Combined permissions array
33
+ * @param accountId - The linked ID of the account.
34
+ * @param roleName - Technical name of the role to assign.
40
35
  */
41
- getEffectivePermissions(accountId: number): Promise<string[]>;
36
+ assignRole(accountId: string, roleName: string): Promise<void>;
42
37
  /**
43
- * Assign a role to an account.
38
+ * Grants a custom permission override to an account.
44
39
  *
45
- * @param accountId - Account ID
46
- * @param roleId - Role ID to assign (null to remove role)
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 role - Role with base permissions
54
- * @param customPerms - Account custom permissions
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
- private combinePermissions;
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
- * Find account by username (for credentials auth)
63
- * Note: This is a basic implementation. For production, add an index on username.
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
- findByUsername(_username: string): Promise<Account | null>;
56
+ removeCustomPermission(accountId: string, permission: string): Promise<void>;
66
57
  /**
67
- * Update account identifiers (for credentials auth identifier merging)
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
- updateIdentifiers(): Promise<void>;
63
+ ban(accountId: string, options?: {
64
+ reason?: string;
65
+ durationMs?: number;
66
+ }): Promise<void>;
70
67
  /**
71
- * Create account with credentials (for CredentialsAuthProvider)
68
+ * Lifts an active ban from an account.
69
+ *
70
+ * @param accountId - The linked ID of the account.
72
71
  */
73
- createWithCredentials(input: {
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
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.AccountService = void 0;
13
- const tsyringe_1 = require("tsyringe");
14
- const framework_1 = require("@open-core/framework");
15
- const crypto_1 = require("crypto");
16
- const account_repository_1 = require("../repositories/account.repository");
17
- const role_service_1 = require("./role.service");
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(repo, roleService, config) {
20
- this.repo = repo;
21
- this.roleService = roleService;
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.repo.findById(id);
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.repo.findByLinkedId(linkedId);
29
- }
30
- async findByIdentifier(type, value) {
31
- return this.repo.findByIdentifier(type, value);
46
+ return this.store.findByLinkedId(linkedId);
32
47
  }
33
- async findOrCreate(identifiers) {
34
- const existing = await this.lookupExisting(identifiers);
35
- if (existing) {
36
- return { account: existing, isNew: false };
37
- }
38
- // Get default role for new accounts
39
- const defaultRole = await this.roleService.getDefaultRole();
40
- // Auto-generate linkedId by default (UUID format for local accounts)
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
- * Add a custom permission to an account (override/additional to role).
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 - Account ID
72
- * @param permission - Permission string to add
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.repo.findById(accountId);
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.repo.updateCustomPermissions(accountId, Array.from(permissions));
72
+ await this.store.update(accountId, {
73
+ customPermissions: Array.from(permissions),
74
+ });
81
75
  }
82
76
  /**
83
- * Remove a custom permission from an account.
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 - Account ID
86
- * @param permission - Permission string to remove
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.repo.findById(accountId);
86
+ const account = await this.store.findByLinkedId(accountId);
90
87
  if (!account)
91
88
  return;
92
- const filtered = (account.customPermissions ?? []).filter((p) => p !== permission && p !== `-${permission}`);
93
- await this.repo.updateCustomPermissions(accountId, filtered);
94
- }
95
- /**
96
- * Get effective permissions for an account (role + custom).
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
- * Assign a role to an account.
96
+ * Prohibits an account from connecting to the server.
109
97
  *
110
- * @param accountId - Account ID
111
- * @param roleId - Role ID to assign (null to remove role)
98
+ * @param accountId - The linked ID of the account.
99
+ * @param options - Ban details including optional reason and duration.
112
100
  */
113
- async assignRole(accountId, roleId) {
114
- await this.repo.updateRole(accountId, roleId);
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
- * Combine role permissions with account custom permissions.
118
- * Custom permissions starting with '-' negate the base permission.
108
+ * Lifts an active ban from an account.
119
109
  *
120
- * @param role - Role with base permissions
121
- * @param customPerms - Account custom permissions
122
- * @returns Combined permissions array
110
+ * @param accountId - The linked ID of the account.
123
111
  */
124
- combinePermissions(role, customPerms) {
125
- const base = new Set(role?.permissions ?? []);
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
- exports.AccountService = AccountService;
203
- exports.AccountService = AccountService = __decorate([
204
- (0, tsyringe_1.injectable)(),
205
- __metadata("design:paramtypes", [account_repository_1.AccountRepository,
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 };