@open-core/identity 1.2.5 → 1.2.6

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.
@@ -8,7 +8,7 @@ import type { IdentityAccount, IdentityRole } from "./types";
8
8
  *
9
9
  * @public
10
10
  */
11
- export declare abstract class IdentityStore {
11
+ export declare abstract class IdentityStore<TId = any, TLinkedId = any, TRoleId = any> {
12
12
  /**
13
13
  * Retrieves an account by its primary connection identifier.
14
14
  *
@@ -16,13 +16,20 @@ export declare abstract class IdentityStore {
16
16
  * @returns A promise resolving to the account or null if not found.
17
17
  */
18
18
  abstract findByIdentifier(identifier: string): Promise<IdentityAccount | null>;
19
+ /**
20
+ * Retrieves an account by its unique numeric or internal ID.
21
+ *
22
+ * @param id - The internal account identifier (database ID).
23
+ * @returns A promise resolving to the account or null if not found.
24
+ */
25
+ abstract findById(id: TId): Promise<IdentityAccount | null>;
19
26
  /**
20
27
  * Retrieves an account by its linked stable ID.
21
28
  *
22
29
  * @param linkedId - The stable ID (e.g., a UUID).
23
30
  * @returns A promise resolving to the account or null if not found.
24
31
  */
25
- abstract findByLinkedId(linkedId: string): Promise<IdentityAccount | null>;
32
+ abstract findByLinkedId(linkedId: TLinkedId): Promise<IdentityAccount | null>;
26
33
  /**
27
34
  * Retrieves an account by its unique username.
28
35
  *
@@ -30,6 +37,19 @@ export declare abstract class IdentityStore {
30
37
  * @returns A promise resolving to the account or null if not found.
31
38
  */
32
39
  abstract findByUsername(username: string): Promise<IdentityAccount | null>;
40
+ /**
41
+ * Retrieves all accounts that are currently banned.
42
+ *
43
+ * @returns A promise resolving to an array of banned accounts.
44
+ */
45
+ abstract findBanned(): Promise<IdentityAccount[]>;
46
+ /**
47
+ * Retrieves all accounts assigned to a specific role.
48
+ *
49
+ * @param roleId - The role identifier.
50
+ * @returns A promise resolving to an array of accounts.
51
+ */
52
+ abstract findByRole(roleId: TRoleId): Promise<IdentityAccount[]>;
33
53
  /**
34
54
  * Persists a new identity account.
35
55
  *
@@ -37,7 +57,7 @@ export declare abstract class IdentityStore {
37
57
  * @returns A promise resolving to the fully created account object.
38
58
  */
39
59
  abstract create(data: Omit<IdentityAccount, "id"> & {
40
- id?: string | number;
60
+ id?: TId;
41
61
  passwordHash?: string;
42
62
  }): Promise<IdentityAccount>;
43
63
  /**
@@ -46,7 +66,7 @@ export declare abstract class IdentityStore {
46
66
  * @param id - The internal account ID.
47
67
  * @param data - Partial object containing fields to update.
48
68
  */
49
- abstract update(id: string | number, data: Partial<Omit<IdentityAccount, "id">>): Promise<void>;
69
+ abstract update(id: TId, data: Partial<Omit<IdentityAccount, "id">>): Promise<void>;
50
70
  /**
51
71
  * Prohibits or allows an account from connecting.
52
72
  *
@@ -55,7 +75,7 @@ export declare abstract class IdentityStore {
55
75
  * @param reason - Optional explanation for the ban.
56
76
  * @param expiresAt - Optional expiration timestamp.
57
77
  */
58
- abstract setBan(id: string | number, banned: boolean, reason?: string, expiresAt?: Date | null): Promise<void>;
78
+ abstract setBan(id: TId, banned: boolean, reason?: string, expiresAt?: Date | null): Promise<void>;
59
79
  }
60
80
  /**
61
81
  * Persistence contract for security roles.
@@ -65,14 +85,35 @@ export declare abstract class IdentityStore {
65
85
  *
66
86
  * @public
67
87
  */
68
- export declare abstract class RoleStore {
88
+ export declare abstract class RoleStore<TId = any> {
69
89
  /**
70
90
  * Retrieves a role definition by its technical identifier.
71
91
  *
72
92
  * @param id - Technical identifier (e.g., 'admin' or 1).
73
93
  * @returns A promise resolving to the role or null if not found.
74
94
  */
75
- abstract findById(id: string | number): Promise<IdentityRole | null>;
95
+ abstract findById(id: TId): Promise<IdentityRole | null>;
96
+ abstract findByName(name: string): Promise<IdentityRole | null>;
97
+ /**
98
+ * Retrieves a role by its hierarchical rank.
99
+ *
100
+ * @param rank - The numeric rank to search for.
101
+ * @returns A promise resolving to the role or null if not found.
102
+ */
103
+ abstract findByRank(rank: number): Promise<IdentityRole | null>;
104
+ /**
105
+ * Retrieves all roles that grant a specific permission.
106
+ *
107
+ * @param permission - The permission string to search for.
108
+ * @returns A promise resolving to an array of roles.
109
+ */
110
+ abstract findByPermission(permission: string): Promise<IdentityRole[]>;
111
+ /**
112
+ * Retrieves all registered roles in the system.
113
+ *
114
+ * @returns A promise resolving to an array of all roles.
115
+ */
116
+ abstract findAll(): Promise<IdentityRole[]>;
76
117
  /**
77
118
  * Resolves the default role for newly connected accounts.
78
119
  *
@@ -86,7 +127,7 @@ export declare abstract class RoleStore {
86
127
  * @returns A promise resolving to the fully created role object.
87
128
  */
88
129
  abstract create(role: Omit<IdentityRole, "id"> & {
89
- id?: string | number;
130
+ id?: TId;
90
131
  }): Promise<IdentityRole>;
91
132
  /**
92
133
  * Updates an existing role definition.
@@ -94,11 +135,11 @@ export declare abstract class RoleStore {
94
135
  * @param id - Technical identifier of the role to update.
95
136
  * @param role - Partial role object containing the fields to modify.
96
137
  */
97
- abstract update(id: string | number, role: Partial<Omit<IdentityRole, "id">>): Promise<void>;
138
+ abstract update(id: TId, role: Partial<Omit<IdentityRole, "id">>): Promise<void>;
98
139
  /**
99
140
  * Removes a role from the system.
100
141
  *
101
142
  * @param id - Technical identifier of the role to delete.
102
143
  */
103
- abstract delete(id: string | number): Promise<void>;
144
+ abstract delete(id: TId): Promise<void>;
104
145
  }
package/dist/index.js CHANGED
@@ -93,10 +93,57 @@ export var Identity;
93
93
  // Configure Principal SPI based on mode
94
94
  if (options.principal.mode === "api") {
95
95
  Server.setPrincipalProvider(ApiPrincipalImpl);
96
+ if (options.principal.defaultRole && typeof options.principal.defaultRole !== "string") {
97
+ throw new Error("[OpenCore-Identity] In 'api' principal mode, 'defaultRole' must be a string (the ID returned by the API).");
98
+ }
96
99
  }
97
100
  else {
98
101
  Server.setPrincipalProvider(PrincipalProviderImpl);
102
+ // Handle default role auto-creation or validation
103
+ const defaultRole = options.principal.defaultRole;
104
+ if (typeof defaultRole === "object") {
105
+ const roles = options.principal.roles || {};
106
+ const defaultId = "default_auto";
107
+ // Inject the role into the configuration if it doesn't exist
108
+ if (!roles[defaultId]) {
109
+ options.principal.roles = {
110
+ ...roles,
111
+ [defaultId]: { ...defaultRole, id: defaultId },
112
+ };
113
+ options.principal.defaultRole = defaultId;
114
+ console.log(`[OpenCore-Identity] Default role '${defaultId}' created from configuration.`);
115
+ }
116
+ }
99
117
  }
118
+ // Handle onReady and waitFor
119
+ const runInitialization = async () => {
120
+ // 1. Wait for dependencies if specified
121
+ if (options.hooks?.waitFor) {
122
+ const waits = Array.isArray(options.hooks.waitFor)
123
+ ? options.hooks.waitFor
124
+ : [options.hooks.waitFor];
125
+ try {
126
+ await Promise.all(waits);
127
+ }
128
+ catch (err) {
129
+ console.error("[OpenCore-Identity] Error waiting for dependencies in 'waitFor':", err);
130
+ return;
131
+ }
132
+ }
133
+ // 2. Execute onReady hook
134
+ if (options.hooks?.onReady) {
135
+ const accountService = container.resolve(AccountServiceImpl);
136
+ const roleService = container.resolve(RoleServiceImpl);
137
+ try {
138
+ await options.hooks.onReady({ accounts: accountService, roles: roleService, container });
139
+ }
140
+ catch (err) {
141
+ console.error("[OpenCore-Identity] Error in onReady hook:", err);
142
+ }
143
+ }
144
+ };
145
+ // Execute the async flow without blocking the main install call
146
+ runInitialization();
100
147
  }
101
148
  Identity.install = install;
102
149
  })(Identity || (Identity = {}));
@@ -1,6 +1,5 @@
1
1
  import { Server } from "@open-core/framework";
2
- import { IdentityStore } from "../../contracts";
3
- import type { IdentityOptions } from "../../types";
2
+ import { IdentityStore, RoleStore } from "../../contracts";
4
3
  /**
5
4
  * Authentication provider for username and password credentials.
6
5
  *
@@ -12,17 +11,11 @@ import type { IdentityOptions } from "../../types";
12
11
  * @public
13
12
  */
14
13
  export declare class CredentialsAuthProvider extends Server.AuthProviderContract {
15
- private readonly options;
16
- private readonly store;
14
+ private readonly accountStore;
15
+ private readonly roleStore;
17
16
  /** Cost factor for bcrypt hashing */
18
17
  private readonly saltRounds;
19
- /**
20
- * Initializes a new instance of the CredentialsAuthProvider.
21
- *
22
- * @param options - Identity system configuration options.
23
- * @param store - Persistence layer for account and credential data.
24
- */
25
- constructor(options: IdentityOptions, store: IdentityStore);
18
+ constructor(accountStore: IdentityStore<string, string, string>, roleStore: RoleStore<string>);
26
19
  /**
27
20
  * Authenticates a player using a username and password.
28
21
  *
@@ -7,13 +7,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
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";
10
+ import { injectable } from "tsyringe";
14
11
  import { Server } from "@open-core/framework";
15
- import { IDENTITY_OPTIONS } from "../../tokens";
16
- import { IdentityStore } from "../../contracts";
12
+ import { IdentityStore, RoleStore } from "../../contracts";
17
13
  import bcrypt from "bcryptjs";
18
14
  /**
19
15
  * Authentication provider for username and password credentials.
@@ -26,16 +22,10 @@ import bcrypt from "bcryptjs";
26
22
  * @public
27
23
  */
28
24
  let CredentialsAuthProvider = class CredentialsAuthProvider extends Server.AuthProviderContract {
29
- /**
30
- * Initializes a new instance of the CredentialsAuthProvider.
31
- *
32
- * @param options - Identity system configuration options.
33
- * @param store - Persistence layer for account and credential data.
34
- */
35
- constructor(options, store) {
25
+ constructor(accountStore, roleStore) {
36
26
  super();
37
- this.options = options;
38
- this.store = store;
27
+ this.accountStore = accountStore;
28
+ this.roleStore = roleStore;
39
29
  /** Cost factor for bcrypt hashing */
40
30
  this.saltRounds = 10;
41
31
  }
@@ -52,9 +42,9 @@ let CredentialsAuthProvider = class CredentialsAuthProvider extends Server.AuthP
52
42
  if (!username || !password) {
53
43
  return { success: false, error: "Username and password are required" };
54
44
  }
55
- const account = await this.store.findByUsername(username);
45
+ const account = await this.accountStore.findByUsername(username);
56
46
  if (!account) {
57
- return { success: false, error: "Invalid credentials" };
47
+ return { success: false, error: "Account not found" };
58
48
  }
59
49
  const passwordHash = account.passwordHash;
60
50
  if (!passwordHash) {
@@ -62,6 +52,7 @@ let CredentialsAuthProvider = class CredentialsAuthProvider extends Server.AuthP
62
52
  }
63
53
  const isValid = await bcrypt.compare(password, passwordHash);
64
54
  if (!isValid) {
55
+ console.log(`compared and is not the same password!`);
65
56
  return { success: false, error: "Invalid credentials" };
66
57
  }
67
58
  if (this.isBanned(account)) {
@@ -84,18 +75,19 @@ let CredentialsAuthProvider = class CredentialsAuthProvider extends Server.AuthP
84
75
  if (!username || !password) {
85
76
  return { success: false, error: "Username and password are required" };
86
77
  }
87
- const existing = await this.store.findByUsername(username);
78
+ const existing = await this.accountStore.findByUsername(username);
88
79
  if (existing) {
89
80
  return { success: false, error: "Username already taken" };
90
81
  }
91
82
  const passwordHash = await bcrypt.hash(password, this.saltRounds);
92
83
  const identifiers = player.getIdentifiers();
93
84
  const primaryIdentifier = identifiers[0] || `internal:${username}`;
94
- const account = await this.store.create({
85
+ const defaultRole = await this.roleStore.getDefaultRole();
86
+ const account = await this.accountStore.create({
95
87
  username,
96
88
  passwordHash,
97
89
  identifier: primaryIdentifier,
98
- roleId: this.options.principal.defaultRole || "user",
90
+ roleId: defaultRole.id,
99
91
  customPermissions: [],
100
92
  isBanned: false,
101
93
  });
@@ -113,7 +105,7 @@ let CredentialsAuthProvider = class CredentialsAuthProvider extends Server.AuthP
113
105
  const accountId = player.accountID;
114
106
  if (!accountId)
115
107
  return { success: false, error: "Not authenticated" };
116
- const account = await this.store.findByLinkedId(accountId);
108
+ const account = await this.accountStore.findByLinkedId(accountId);
117
109
  if (!account || this.isBanned(account)) {
118
110
  return { success: false, error: "Session invalid or account banned" };
119
111
  }
@@ -125,7 +117,7 @@ let CredentialsAuthProvider = class CredentialsAuthProvider extends Server.AuthP
125
117
  * @param player - The framework player entity.
126
118
  */
127
119
  async logout(player) {
128
- // Session state is managed by the framework.
120
+ player.unlinkAccount();
129
121
  }
130
122
  /**
131
123
  * Internal helper to determine if an account is currently prohibited.
@@ -145,7 +137,7 @@ let CredentialsAuthProvider = class CredentialsAuthProvider extends Server.AuthP
145
137
  };
146
138
  CredentialsAuthProvider = __decorate([
147
139
  injectable(),
148
- __param(0, inject(IDENTITY_OPTIONS)),
149
- __metadata("design:paramtypes", [Object, IdentityStore])
140
+ __metadata("design:paramtypes", [IdentityStore,
141
+ RoleStore])
150
142
  ], CredentialsAuthProvider);
151
143
  export { CredentialsAuthProvider };
@@ -28,7 +28,6 @@ interface AuthResult {
28
28
  export declare class LocalAuthProvider extends Server.AuthProviderContract {
29
29
  private readonly options;
30
30
  private readonly store;
31
- private readonly config;
32
31
  /**
33
32
  * Initializes a new instance of the IdentityAuthProvider.
34
33
  *
@@ -36,7 +35,7 @@ export declare class LocalAuthProvider extends Server.AuthProviderContract {
36
35
  * @param store - Persistence layer for account data.
37
36
  * @param config - Framework configuration service.
38
37
  */
39
- constructor(options: IdentityOptions, store: IdentityStore, config: Server.ConfigService);
38
+ constructor(options: IdentityOptions, store: IdentityStore);
40
39
  /**
41
40
  * Authenticates a player based on the configured strategy.
42
41
  *
@@ -31,11 +31,10 @@ let LocalAuthProvider = class LocalAuthProvider extends Server.AuthProviderContr
31
31
  * @param store - Persistence layer for account data.
32
32
  * @param config - Framework configuration service.
33
33
  */
34
- constructor(options, store, config) {
34
+ constructor(options, store) {
35
35
  super();
36
36
  this.options = options;
37
37
  this.store = store;
38
- this.config = config;
39
38
  }
40
39
  /**
41
40
  * Authenticates a player based on the configured strategy.
@@ -147,6 +146,6 @@ let LocalAuthProvider = class LocalAuthProvider extends Server.AuthProviderContr
147
146
  LocalAuthProvider = __decorate([
148
147
  injectable(),
149
148
  __param(0, inject(IDENTITY_OPTIONS)),
150
- __metadata("design:paramtypes", [Object, IdentityStore, Server.ConfigService])
149
+ __metadata("design:paramtypes", [Object, IdentityStore])
151
150
  ], LocalAuthProvider);
152
151
  export { LocalAuthProvider };
@@ -116,7 +116,10 @@ let IdentityPrincipalProvider = class IdentityPrincipalProvider extends Server.P
116
116
  if (!role) {
117
117
  const defaultRoleId = this.options.principal.defaultRole;
118
118
  if (defaultRoleId !== undefined && defaultRoleId !== null && defaultRoleId !== "") {
119
- role = this.options.principal.roles?.[defaultRoleId];
119
+ // We ensure defaultRoleId is a valid key (string | number) because Identity.install
120
+ // converts any IdentityRole object into a registered 'default_auto' string ID.
121
+ const roleKey = typeof defaultRoleId === "object" ? "default_auto" : defaultRoleId;
122
+ role = this.options.principal.roles?.[roleKey];
120
123
  if (!role && this.roleStore && this.options.principal.mode === "db") {
121
124
  role = await this.roleStore.getDefaultRole();
122
125
  }
@@ -1,4 +1,4 @@
1
- import { IdentityStore } from "../contracts";
1
+ import { IdentityStore, RoleStore } from "../contracts";
2
2
  import type { IdentityAccount } from "../types";
3
3
  /**
4
4
  * High-level service for managing identity accounts and security policies.
@@ -9,48 +9,33 @@ import type { IdentityAccount } from "../types";
9
9
  * @public
10
10
  * @injectable
11
11
  */
12
- export declare class AccountService {
13
- private readonly store;
14
- constructor(store: IdentityStore);
12
+ export declare class AccountService<TId = any, TLinkedId = any, TRoleId = any> {
13
+ readonly store: IdentityStore<TId, TLinkedId, TRoleId>;
14
+ constructor(store: IdentityStore<TId, TLinkedId, TRoleId>);
15
15
  /**
16
- * Retrieves an account by its unique numeric or internal ID.
16
+ * Retrieves all accounts assigned to a specific role.
17
17
  *
18
- * @param id - The internal account identifier.
19
- * @returns A promise resolving to the account or null if not found.
18
+ * @param roleId - The role identifier.
19
+ * @returns A promise resolving to an array of accounts.
20
20
  */
21
- findById(id: string): Promise<IdentityAccount | null>;
21
+ findByRole(roleId: TRoleId): Promise<IdentityAccount[]>;
22
22
  /**
23
- * Retrieves an account by its stable linked ID.
23
+ * Retrieves all accounts that are currently prohibited from connecting.
24
24
  *
25
- * @param linkedId - The stable ID (UUID or external system ID).
26
- * @returns A promise resolving to the account or null if not found.
25
+ * @returns A promise resolving to an array of banned accounts.
27
26
  */
28
- findByLinkedId(linkedId: string): Promise<IdentityAccount | null>;
29
- /**
30
- * Persists a new identity account.
31
- *
32
- * @param data - Initial account properties. ID can be provided or left to the store.
33
- * @returns A promise resolving to the fully created account object.
34
- */
35
- create(data: Omit<IdentityAccount, "id"> & {
36
- id?: string | number;
37
- passwordHash?: string;
38
- }): Promise<IdentityAccount>;
39
- /**
40
- * Updates an existing account's metadata or status.
41
- *
42
- * @param id - The internal account ID.
43
- * @param data - Partial object containing fields to update.
44
- * @returns A promise that resolves when the update is complete.
45
- */
46
- update(id: string | number, data: Partial<Omit<IdentityAccount, "id">>): Promise<void>;
27
+ findBanned(): Promise<IdentityAccount[]>;
28
+ assignRole(accountId: TId, roleId: TRoleId, options?: {
29
+ clearCustomPermissions?: boolean;
30
+ }): Promise<void>;
47
31
  /**
48
- * Assigns a security role to an account.
32
+ * Checks if an account has a specific permission, considering both role and custom overrides.
49
33
  *
50
- * @param accountId - The unique ID of the account.
51
- * @param roleId - Technical identifier of the role to assign.
34
+ * @param accountId - The account identifier.
35
+ * @param permission - The permission string to check.
36
+ * @param roleStore - Required to resolve the role's base permissions.
52
37
  */
53
- assignRole(accountId: string | number, roleId: string | number): Promise<void>;
38
+ hasPermission(linkedID: TLinkedId, permission: string, roleStore: RoleStore): Promise<boolean>;
54
39
  /**
55
40
  * Grants a custom permission override to an account.
56
41
  *
@@ -60,24 +45,24 @@ export declare class AccountService {
60
45
  * @param accountId - The linked ID of the account.
61
46
  * @param permission - The permission string to grant.
62
47
  */
63
- addCustomPermission(accountId: string, permission: string): Promise<void>;
48
+ addCustomPermission(linkedID: TLinkedId, permission: string): Promise<void>;
64
49
  /**
65
50
  * Revokes a custom permission override.
66
51
  *
67
52
  * To explicitly deny a permission that a role might grant, use the `-` prefix
68
53
  * (e.g., `-chat.use`).
69
54
  *
70
- * @param accountId - The linked ID of the account.
55
+ * @param linkedID - The linked ID of the account.
71
56
  * @param permission - The permission string to remove or revoke.
72
57
  */
73
- removeCustomPermission(accountId: string, permission: string): Promise<void>;
58
+ removeCustomPermission(linkedID: TLinkedId, permission: string): Promise<void>;
74
59
  /**
75
60
  * Prohibits an account from connecting to the server.
76
61
  *
77
62
  * @param accountId - The linked ID of the account.
78
63
  * @param options - Ban details including optional reason and duration.
79
64
  */
80
- ban(accountId: string, options?: {
65
+ ban(accountId: TId, options?: {
81
66
  reason?: string;
82
67
  durationMs?: number;
83
68
  }): Promise<void>;
@@ -86,5 +71,5 @@ export declare class AccountService {
86
71
  *
87
72
  * @param accountId - The linked ID of the account.
88
73
  */
89
- unban(accountId: string): Promise<void>;
74
+ unban(accountId: TId): Promise<void>;
90
75
  }
@@ -23,50 +23,53 @@ let AccountService = class AccountService {
23
23
  this.store = store;
24
24
  }
25
25
  /**
26
- * Retrieves an account by its unique numeric or internal ID.
26
+ * Retrieves all accounts assigned to a specific role.
27
27
  *
28
- * @param id - The internal account identifier.
29
- * @returns A promise resolving to the account or null if not found.
28
+ * @param roleId - The role identifier.
29
+ * @returns A promise resolving to an array of accounts.
30
30
  */
31
- async findById(id) {
32
- return this.store.findByLinkedId(id); // Using linkedId as the primary public handle
31
+ async findByRole(roleId) {
32
+ return this.store.findByRole(roleId);
33
33
  }
34
34
  /**
35
- * Retrieves an account by its stable linked ID.
35
+ * Retrieves all accounts that are currently prohibited from connecting.
36
36
  *
37
- * @param linkedId - The stable ID (UUID or external system ID).
38
- * @returns A promise resolving to the account or null if not found.
37
+ * @returns A promise resolving to an array of banned accounts.
39
38
  */
40
- async findByLinkedId(linkedId) {
41
- return this.store.findByLinkedId(linkedId);
39
+ async findBanned() {
40
+ return this.store.findBanned();
42
41
  }
43
- /**
44
- * Persists a new identity account.
45
- *
46
- * @param data - Initial account properties. ID can be provided or left to the store.
47
- * @returns A promise resolving to the fully created account object.
48
- */
49
- async create(data) {
50
- return this.store.create(data);
42
+ async assignRole(accountId, roleId, options = {}) {
43
+ const updateData = { roleId };
44
+ if (options.clearCustomPermissions) {
45
+ updateData.customPermissions = [];
46
+ }
47
+ await this.store.update(accountId, updateData);
51
48
  }
52
49
  /**
53
- * Updates an existing account's metadata or status.
50
+ * Checks if an account has a specific permission, considering both role and custom overrides.
54
51
  *
55
- * @param id - The internal account ID.
56
- * @param data - Partial object containing fields to update.
57
- * @returns A promise that resolves when the update is complete.
52
+ * @param accountId - The account identifier.
53
+ * @param permission - The permission string to check.
54
+ * @param roleStore - Required to resolve the role's base permissions.
58
55
  */
59
- async update(id, data) {
60
- await this.store.update(id, data);
61
- }
62
- /**
63
- * Assigns a security role to an account.
64
- *
65
- * @param accountId - The unique ID of the account.
66
- * @param roleId - Technical identifier of the role to assign.
67
- */
68
- async assignRole(accountId, roleId) {
69
- await this.update(accountId, { roleId });
56
+ async hasPermission(linkedID, permission, roleStore) {
57
+ const account = await this.store.findByLinkedId(linkedID);
58
+ if (!account)
59
+ return false;
60
+ if (!account.roleId)
61
+ return account.customPermissions.includes(permission);
62
+ const role = await roleStore.findById(account.roleId);
63
+ const basePermissions = role?.permissions || [];
64
+ // Simple resolution logic (could be more complex with wildcards)
65
+ const permissions = new Set(basePermissions);
66
+ for (const override of account.customPermissions) {
67
+ if (override === `+${permission}` || override === permission)
68
+ return true;
69
+ if (override === `-${permission}`)
70
+ return false;
71
+ }
72
+ return permissions.has(permission) || permissions.has("*");
70
73
  }
71
74
  /**
72
75
  * Grants a custom permission override to an account.
@@ -77,13 +80,13 @@ let AccountService = class AccountService {
77
80
  * @param accountId - The linked ID of the account.
78
81
  * @param permission - The permission string to grant.
79
82
  */
80
- async addCustomPermission(accountId, permission) {
81
- const account = await this.store.findByLinkedId(accountId);
83
+ async addCustomPermission(linkedID, permission) {
84
+ const account = await this.store.findByLinkedId(linkedID);
82
85
  if (!account)
83
86
  return;
84
87
  const permissions = new Set(account.customPermissions);
85
88
  permissions.add(permission);
86
- await this.store.update(accountId, {
89
+ await this.store.update(account.id, {
87
90
  customPermissions: Array.from(permissions),
88
91
  });
89
92
  }
@@ -93,16 +96,16 @@ let AccountService = class AccountService {
93
96
  * To explicitly deny a permission that a role might grant, use the `-` prefix
94
97
  * (e.g., `-chat.use`).
95
98
  *
96
- * @param accountId - The linked ID of the account.
99
+ * @param linkedID - The linked ID of the account.
97
100
  * @param permission - The permission string to remove or revoke.
98
101
  */
99
- async removeCustomPermission(accountId, permission) {
100
- const account = await this.store.findByLinkedId(accountId);
102
+ async removeCustomPermission(linkedID, permission) {
103
+ const account = await this.store.findByLinkedId(linkedID);
101
104
  if (!account)
102
105
  return;
103
106
  const permissions = new Set(account.customPermissions);
104
107
  permissions.delete(permission);
105
- await this.store.update(accountId, {
108
+ await this.store.update(account.id, {
106
109
  customPermissions: Array.from(permissions),
107
110
  });
108
111
  }
@@ -10,38 +10,29 @@ import type { IdentityRole } from "../types";
10
10
  * @public
11
11
  * @injectable
12
12
  */
13
- export declare class RoleService {
14
- private readonly store;
15
- constructor(store: RoleStore);
13
+ export declare class RoleService<TId = any> {
14
+ readonly store: RoleStore<TId>;
15
+ constructor(store: RoleStore<TId>);
16
16
  /**
17
- * Persists a new security role definition.
17
+ * Retrieves all roles that grant a specific permission.
18
18
  *
19
- * @param role - The initial role properties (ID is optional).
20
- * @returns A promise resolving to the created role.
19
+ * @param permission - The permission string to search for.
20
+ * @returns A promise resolving to an array of roles.
21
21
  */
22
- create(role: Omit<IdentityRole, "id"> & {
23
- id?: string | number;
24
- }): Promise<IdentityRole>;
22
+ findByPermission(permission: string): Promise<IdentityRole[]>;
25
23
  /**
26
- * Updates an existing role's rank or permissions.
24
+ * Retrieves a role by its hierarchical rank.
27
25
  *
28
- * @param id - The unique technical identifier of the role to update.
29
- * @param data - Partial object containing the fields to modify.
30
- * @returns A promise that resolves when the update is complete.
26
+ * @param rank - The numeric rank to search for.
27
+ * @returns A promise resolving to the role or null if not found.
31
28
  */
32
- update(id: string | number, data: Partial<Omit<IdentityRole, "id">>): Promise<void>;
29
+ findByRank(rank: number): Promise<IdentityRole | null>;
33
30
  /**
34
- * Permanently removes a role definition from the system.
31
+ * Checks if a role is higher or equal than another based on rank.
35
32
  *
36
- * @param id - The technical identifier of the role to delete.
37
- * @returns A promise that resolves when the role is deleted.
33
+ * @param roleId - The role to check.
34
+ * @param requiredRoleId - The required role.
35
+ * @returns True if roleId has equal or higher rank.
38
36
  */
39
- delete(id: string | number): Promise<void>;
40
- /**
41
- * Retrieves the full list of permissions granted to a specific role.
42
- *
43
- * @param id - The technical identifier of the role.
44
- * @returns A promise resolving to an array of permission strings.
45
- */
46
- getPermissions(id: string | number): Promise<string[]>;
37
+ isHigherOrEqual(roleId: TId, requiredRoleId: TId): Promise<boolean>;
47
38
  }
@@ -24,42 +24,38 @@ let RoleService = class RoleService {
24
24
  this.store = store;
25
25
  }
26
26
  /**
27
- * Persists a new security role definition.
27
+ * Retrieves all roles that grant a specific permission.
28
28
  *
29
- * @param role - The initial role properties (ID is optional).
30
- * @returns A promise resolving to the created role.
29
+ * @param permission - The permission string to search for.
30
+ * @returns A promise resolving to an array of roles.
31
31
  */
32
- async create(role) {
33
- return this.store.create(role);
32
+ async findByPermission(permission) {
33
+ return this.store.findByPermission(permission);
34
34
  }
35
35
  /**
36
- * Updates an existing role's rank or permissions.
36
+ * Retrieves a role by its hierarchical rank.
37
37
  *
38
- * @param id - The unique technical identifier of the role to update.
39
- * @param data - Partial object containing the fields to modify.
40
- * @returns A promise that resolves when the update is complete.
38
+ * @param rank - The numeric rank to search for.
39
+ * @returns A promise resolving to the role or null if not found.
41
40
  */
42
- async update(id, data) {
43
- await this.store.update(id, data);
41
+ async findByRank(rank) {
42
+ return this.store.findByRank(rank);
44
43
  }
45
44
  /**
46
- * Permanently removes a role definition from the system.
45
+ * Checks if a role is higher or equal than another based on rank.
47
46
  *
48
- * @param id - The technical identifier of the role to delete.
49
- * @returns A promise that resolves when the role is deleted.
47
+ * @param roleId - The role to check.
48
+ * @param requiredRoleId - The required role.
49
+ * @returns True if roleId has equal or higher rank.
50
50
  */
51
- async delete(id) {
52
- await this.store.delete(id);
53
- }
54
- /**
55
- * Retrieves the full list of permissions granted to a specific role.
56
- *
57
- * @param id - The technical identifier of the role.
58
- * @returns A promise resolving to an array of permission strings.
59
- */
60
- async getPermissions(id) {
61
- const role = await this.store.findById(id);
62
- return role?.permissions || [];
51
+ async isHigherOrEqual(roleId, requiredRoleId) {
52
+ const [role, required] = await Promise.all([
53
+ this.store.findById(roleId),
54
+ this.store.findById(requiredRoleId)
55
+ ]);
56
+ if (!role || !required)
57
+ return false;
58
+ return role.rank >= required.rank;
63
59
  }
64
60
  };
65
61
  RoleService = __decorate([
package/dist/types.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { AccountService } from "./services/account.service";
2
+ import { RoleService } from "./services/role.service";
1
3
  /**
2
4
  * Authentication strategy modes.
3
5
  *
@@ -29,11 +31,13 @@ export type PrincipalMode = "roles" | "db" | "api";
29
31
  *
30
32
  * @public
31
33
  */
32
- export interface IdentityRole {
34
+ export interface IdentityRole<TId = any> {
33
35
  /**
34
- * Technical identifier for the role (e.g., 'admin', 1, 'uuid').
36
+ * Technical identifier for the role (e.g., 1, 'uuid').
35
37
  */
36
- id: string | number;
38
+ id: TId;
39
+ /** name, common used by identifiers 'admin' */
40
+ name: string;
37
41
  /**
38
42
  * Hierarchical weight.
39
43
  *
@@ -79,6 +83,15 @@ export interface IdentityOptions {
79
83
  * @defaultValue 'license'
80
84
  */
81
85
  primaryIdentifier?: string;
86
+ /**
87
+ * The ID of the role assigned to newly created accounts.
88
+ *
89
+ * - If string/number: Used as the ID of an existing role.
90
+ * - If IdentityRole object without ID: A default role will be created automatically.
91
+ *
92
+ * @defaultValue 'user'
93
+ */
94
+ defaultRole?: string | number | Omit<IdentityRole, "id">;
82
95
  };
83
96
  /**
84
97
  * Authorization and permissions configuration.
@@ -95,10 +108,10 @@ export interface IdentityOptions {
95
108
  */
96
109
  roles?: Record<string | number, IdentityRole>;
97
110
  /**
98
- * The ID of the role assigned to newly created accounts.
99
- * @defaultValue 'user'
111
+ * The default role, if you set a ID (external ID) use string type
112
+ * or use IdentityRole without id to create a new
100
113
  */
101
- defaultRole?: string | number;
114
+ defaultRole?: Omit<IdentityRole, 'id'> | string;
102
115
  /**
103
116
  * Time-to-live in milliseconds for cached principal data.
104
117
  *
@@ -108,6 +121,25 @@ export interface IdentityOptions {
108
121
  */
109
122
  cacheTtl?: number;
110
123
  };
124
+ /**
125
+ * Lifecycle hooks for the identity system.
126
+ */
127
+ hooks?: {
128
+ /**
129
+ * Optional promise or array of promises to wait for before finishing initialization.
130
+ * Useful for ensuring database connections are established.
131
+ */
132
+ waitFor?: Promise<any> | Promise<any>[];
133
+ /**
134
+ * Fired when the identity system is fully initialized and registered.
135
+ * Use this to perform database seeding (e.g., creating default roles).
136
+ */
137
+ onReady?: (services: {
138
+ accounts: AccountService;
139
+ roles: RoleService;
140
+ container: any;
141
+ }) => Promise<void> | void;
142
+ };
111
143
  }
112
144
  /**
113
145
  * Represents a persistent identity account.
@@ -117,19 +149,19 @@ export interface IdentityOptions {
117
149
  *
118
150
  * @public
119
151
  */
120
- export interface IdentityAccount {
152
+ export interface IdentityAccount<TId = any, TRoleId = any> {
121
153
  /**
122
154
  * Internal unique database/store ID.
123
155
  */
124
- id: string | number;
156
+ id: TId;
125
157
  /**
126
158
  * Primary connection identifier (e.g., 'license:123...').
127
159
  */
128
- identifier: string;
160
+ identifier?: string;
129
161
  /**
130
162
  * Current technical role ID assigned to this account.
131
163
  */
132
- roleId?: string | number;
164
+ roleId?: TRoleId;
133
165
  /**
134
166
  * Optional technical username for credentials-based authentication.
135
167
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-core/identity",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
4
4
  "description": "Enterprise-grade identity, authentication, and authorization plugin for OpenCore Framework",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "packageManager": "pnpm@10.13.1",
41
41
  "dependencies": {
42
- "@open-core/framework": "^0.2.4",
42
+ "@open-core/framework": "^0.2.7",
43
43
  "bcryptjs": "^3.0.3",
44
44
  "reflect-metadata": "^0.2.2",
45
45
  "tsyringe": "^4.10.0",