@open-core/identity 1.2.4 → 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,13 +37,27 @@ 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
  *
36
- * @param data - Initial account properties.
56
+ * @param data - Initial account properties (ID is optional as it's usually handled by the store).
37
57
  * @returns A promise resolving to the fully created account object.
38
58
  */
39
- abstract create(data: Partial<IdentityAccount> & {
59
+ abstract create(data: Omit<IdentityAccount, "id"> & {
60
+ id?: TId;
40
61
  passwordHash?: string;
41
62
  }): Promise<IdentityAccount>;
42
63
  /**
@@ -45,7 +66,7 @@ export declare abstract class IdentityStore {
45
66
  * @param id - The internal account ID.
46
67
  * @param data - Partial object containing fields to update.
47
68
  */
48
- abstract update(id: string | number, data: Partial<IdentityAccount>): Promise<void>;
69
+ abstract update(id: TId, data: Partial<Omit<IdentityAccount, "id">>): Promise<void>;
49
70
  /**
50
71
  * Prohibits or allows an account from connecting.
51
72
  *
@@ -54,7 +75,7 @@ export declare abstract class IdentityStore {
54
75
  * @param reason - Optional explanation for the ban.
55
76
  * @param expiresAt - Optional expiration timestamp.
56
77
  */
57
- 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>;
58
79
  }
59
80
  /**
60
81
  * Persistence contract for security roles.
@@ -64,14 +85,35 @@ export declare abstract class IdentityStore {
64
85
  *
65
86
  * @public
66
87
  */
67
- export declare abstract class RoleStore {
88
+ export declare abstract class RoleStore<TId = any> {
68
89
  /**
69
90
  * Retrieves a role definition by its technical identifier.
70
91
  *
71
92
  * @param id - Technical identifier (e.g., 'admin' or 1).
72
93
  * @returns A promise resolving to the role or null if not found.
73
94
  */
74
- 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[]>;
75
117
  /**
76
118
  * Resolves the default role for newly connected accounts.
77
119
  *
@@ -79,15 +121,25 @@ export declare abstract class RoleStore {
79
121
  */
80
122
  abstract getDefaultRole(): Promise<IdentityRole>;
81
123
  /**
82
- * Creates or updates a role definition.
124
+ * Persists a new security role definition.
125
+ *
126
+ * @param role - Initial role properties. ID can be provided or left to the store.
127
+ * @returns A promise resolving to the fully created role object.
128
+ */
129
+ abstract create(role: Omit<IdentityRole, "id"> & {
130
+ id?: TId;
131
+ }): Promise<IdentityRole>;
132
+ /**
133
+ * Updates an existing role definition.
83
134
  *
84
- * @param role - The complete role object.
135
+ * @param id - Technical identifier of the role to update.
136
+ * @param role - Partial role object containing the fields to modify.
85
137
  */
86
- abstract save(role: IdentityRole): Promise<void>;
138
+ abstract update(id: TId, role: Partial<Omit<IdentityRole, "id">>): Promise<void>;
87
139
  /**
88
140
  * Removes a role from the system.
89
141
  *
90
142
  * @param id - Technical identifier of the role to delete.
91
143
  */
92
- abstract delete(id: string | number): Promise<void>;
144
+ abstract delete(id: TId): Promise<void>;
93
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,21 @@ 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,
91
+ customPermissions: [],
92
+ isBanned: false,
99
93
  });
100
94
  const accountIdStr = String(account.id);
101
95
  player.linkAccount(accountIdStr);
@@ -111,7 +105,7 @@ let CredentialsAuthProvider = class CredentialsAuthProvider extends Server.AuthP
111
105
  const accountId = player.accountID;
112
106
  if (!accountId)
113
107
  return { success: false, error: "Not authenticated" };
114
- const account = await this.store.findByLinkedId(accountId);
108
+ const account = await this.accountStore.findByLinkedId(accountId);
115
109
  if (!account || this.isBanned(account)) {
116
110
  return { success: false, error: "Session invalid or account banned" };
117
111
  }
@@ -123,7 +117,7 @@ let CredentialsAuthProvider = class CredentialsAuthProvider extends Server.AuthP
123
117
  * @param player - The framework player entity.
124
118
  */
125
119
  async logout(player) {
126
- // Session state is managed by the framework.
120
+ player.unlinkAccount();
127
121
  }
128
122
  /**
129
123
  * Internal helper to determine if an account is currently prohibited.
@@ -143,7 +137,7 @@ let CredentialsAuthProvider = class CredentialsAuthProvider extends Server.AuthP
143
137
  };
144
138
  CredentialsAuthProvider = __decorate([
145
139
  injectable(),
146
- __param(0, inject(IDENTITY_OPTIONS)),
147
- __metadata("design:paramtypes", [Object, IdentityStore])
140
+ __metadata("design:paramtypes", [IdentityStore,
141
+ RoleStore])
148
142
  ], CredentialsAuthProvider);
149
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.
@@ -115,6 +114,8 @@ let LocalAuthProvider = class LocalAuthProvider extends Server.AuthProviderContr
115
114
  account = await this.store.create({
116
115
  identifier: identifierValue,
117
116
  roleId: "user",
117
+ customPermissions: [],
118
+ isBanned: false,
118
119
  });
119
120
  isNew = true;
120
121
  }
@@ -145,6 +146,6 @@ let LocalAuthProvider = class LocalAuthProvider extends Server.AuthProviderContr
145
146
  LocalAuthProvider = __decorate([
146
147
  injectable(),
147
148
  __param(0, inject(IDENTITY_OPTIONS)),
148
- __metadata("design:paramtypes", [Object, IdentityStore, Server.ConfigService])
149
+ __metadata("design:paramtypes", [Object, IdentityStore])
149
150
  ], LocalAuthProvider);
150
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,5 +1,5 @@
1
- import { IdentityStore } from "../contracts";
2
- import type { IdentityAccount, IdentityOptions } from "../types";
1
+ import { IdentityStore, RoleStore } from "../contracts";
2
+ import type { IdentityAccount } from "../types";
3
3
  /**
4
4
  * High-level service for managing identity accounts and security policies.
5
5
  *
@@ -9,31 +9,33 @@ import type { IdentityAccount, IdentityOptions } from "../types";
9
9
  * @public
10
10
  * @injectable
11
11
  */
12
- export declare class AccountService {
13
- private readonly store;
14
- private readonly options;
15
- constructor(store: IdentityStore, options: IdentityOptions);
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>);
16
15
  /**
17
- * Retrieves an account by its unique numeric or internal ID.
16
+ * Retrieves all accounts assigned to a specific role.
18
17
  *
19
- * @param id - The internal account identifier.
20
- * @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.
21
20
  */
22
- findById(id: string): Promise<IdentityAccount | null>;
21
+ findByRole(roleId: TRoleId): Promise<IdentityAccount[]>;
23
22
  /**
24
- * Retrieves an account by its stable linked ID.
23
+ * Retrieves all accounts that are currently prohibited from connecting.
25
24
  *
26
- * @param linkedId - The stable ID (UUID or external system ID).
27
- * @returns A promise resolving to the account or null if not found.
25
+ * @returns A promise resolving to an array of banned accounts.
28
26
  */
29
- findByLinkedId(linkedId: string): Promise<IdentityAccount | null>;
27
+ findBanned(): Promise<IdentityAccount[]>;
28
+ assignRole(accountId: TId, roleId: TRoleId, options?: {
29
+ clearCustomPermissions?: boolean;
30
+ }): Promise<void>;
30
31
  /**
31
- * Assigns a security role to an account.
32
+ * Checks if an account has a specific permission, considering both role and custom overrides.
32
33
  *
33
- * @param accountId - The unique ID of the account.
34
- * @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.
35
37
  */
36
- assignRole(accountId: string | number, roleId: string | number): Promise<void>;
38
+ hasPermission(linkedID: TLinkedId, permission: string, roleStore: RoleStore): Promise<boolean>;
37
39
  /**
38
40
  * Grants a custom permission override to an account.
39
41
  *
@@ -43,24 +45,24 @@ export declare class AccountService {
43
45
  * @param accountId - The linked ID of the account.
44
46
  * @param permission - The permission string to grant.
45
47
  */
46
- addCustomPermission(accountId: string, permission: string): Promise<void>;
48
+ addCustomPermission(linkedID: TLinkedId, permission: string): Promise<void>;
47
49
  /**
48
50
  * Revokes a custom permission override.
49
51
  *
50
52
  * To explicitly deny a permission that a role might grant, use the `-` prefix
51
53
  * (e.g., `-chat.use`).
52
54
  *
53
- * @param accountId - The linked ID of the account.
55
+ * @param linkedID - The linked ID of the account.
54
56
  * @param permission - The permission string to remove or revoke.
55
57
  */
56
- removeCustomPermission(accountId: string, permission: string): Promise<void>;
58
+ removeCustomPermission(linkedID: TLinkedId, permission: string): Promise<void>;
57
59
  /**
58
60
  * Prohibits an account from connecting to the server.
59
61
  *
60
62
  * @param accountId - The linked ID of the account.
61
63
  * @param options - Ban details including optional reason and duration.
62
64
  */
63
- ban(accountId: string, options?: {
65
+ ban(accountId: TId, options?: {
64
66
  reason?: string;
65
67
  durationMs?: number;
66
68
  }): Promise<void>;
@@ -69,5 +71,5 @@ export declare class AccountService {
69
71
  *
70
72
  * @param accountId - The linked ID of the account.
71
73
  */
72
- unban(accountId: string): Promise<void>;
74
+ unban(accountId: TId): Promise<void>;
73
75
  }
@@ -7,11 +7,7 @@ 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";
14
- import { IDENTITY_OPTIONS } from "../tokens";
10
+ import { injectable } from "tsyringe";
15
11
  import { IdentityStore } from "../contracts";
16
12
  /**
17
13
  * High-level service for managing identity accounts and security policies.
@@ -23,36 +19,57 @@ import { IdentityStore } from "../contracts";
23
19
  * @injectable
24
20
  */
25
21
  let AccountService = class AccountService {
26
- constructor(store, options) {
22
+ constructor(store) {
27
23
  this.store = store;
28
- this.options = options;
29
24
  }
30
25
  /**
31
- * Retrieves an account by its unique numeric or internal ID.
26
+ * Retrieves all accounts assigned to a specific role.
32
27
  *
33
- * @param id - The internal account identifier.
34
- * @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.
35
30
  */
36
- async findById(id) {
37
- return this.store.findByLinkedId(id); // Using linkedId as the primary public handle
31
+ async findByRole(roleId) {
32
+ return this.store.findByRole(roleId);
38
33
  }
39
34
  /**
40
- * Retrieves an account by its stable linked ID.
35
+ * Retrieves all accounts that are currently prohibited from connecting.
41
36
  *
42
- * @param linkedId - The stable ID (UUID or external system ID).
43
- * @returns A promise resolving to the account or null if not found.
37
+ * @returns A promise resolving to an array of banned accounts.
44
38
  */
45
- async findByLinkedId(linkedId) {
46
- return this.store.findByLinkedId(linkedId);
39
+ async findBanned() {
40
+ return this.store.findBanned();
41
+ }
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);
47
48
  }
48
49
  /**
49
- * Assigns a security role to an account.
50
+ * Checks if an account has a specific permission, considering both role and custom overrides.
50
51
  *
51
- * @param accountId - The unique ID of the account.
52
- * @param roleId - Technical identifier of the role to assign.
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.
53
55
  */
54
- async assignRole(accountId, roleId) {
55
- await this.store.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("*");
56
73
  }
57
74
  /**
58
75
  * Grants a custom permission override to an account.
@@ -63,13 +80,13 @@ let AccountService = class AccountService {
63
80
  * @param accountId - The linked ID of the account.
64
81
  * @param permission - The permission string to grant.
65
82
  */
66
- async addCustomPermission(accountId, permission) {
67
- const account = await this.store.findByLinkedId(accountId);
83
+ async addCustomPermission(linkedID, permission) {
84
+ const account = await this.store.findByLinkedId(linkedID);
68
85
  if (!account)
69
86
  return;
70
87
  const permissions = new Set(account.customPermissions);
71
88
  permissions.add(permission);
72
- await this.store.update(accountId, {
89
+ await this.store.update(account.id, {
73
90
  customPermissions: Array.from(permissions),
74
91
  });
75
92
  }
@@ -79,16 +96,16 @@ let AccountService = class AccountService {
79
96
  * To explicitly deny a permission that a role might grant, use the `-` prefix
80
97
  * (e.g., `-chat.use`).
81
98
  *
82
- * @param accountId - The linked ID of the account.
99
+ * @param linkedID - The linked ID of the account.
83
100
  * @param permission - The permission string to remove or revoke.
84
101
  */
85
- async removeCustomPermission(accountId, permission) {
86
- const account = await this.store.findByLinkedId(accountId);
102
+ async removeCustomPermission(linkedID, permission) {
103
+ const account = await this.store.findByLinkedId(linkedID);
87
104
  if (!account)
88
105
  return;
89
106
  const permissions = new Set(account.customPermissions);
90
107
  permissions.delete(permission);
91
- await this.store.update(accountId, {
108
+ await this.store.update(account.id, {
92
109
  customPermissions: Array.from(permissions),
93
110
  });
94
111
  }
@@ -115,7 +132,6 @@ let AccountService = class AccountService {
115
132
  };
116
133
  AccountService = __decorate([
117
134
  injectable(),
118
- __param(1, inject(IDENTITY_OPTIONS)),
119
- __metadata("design:paramtypes", [IdentityStore, Object])
135
+ __metadata("design:paramtypes", [IdentityStore])
120
136
  ], AccountService);
121
137
  export { AccountService };
@@ -1,5 +1,5 @@
1
1
  import { RoleStore } from "../contracts";
2
- import type { IdentityOptions, IdentityRole } from "../types";
2
+ import type { IdentityRole } from "../types";
3
3
  /**
4
4
  * High-level service for managing security roles and their associated permissions.
5
5
  *
@@ -10,43 +10,29 @@ import type { IdentityOptions, IdentityRole } from "../types";
10
10
  * @public
11
11
  * @injectable
12
12
  */
13
- export declare class RoleService {
14
- private readonly store;
15
- private readonly options;
13
+ export declare class RoleService<TId = any> {
14
+ readonly store: RoleStore<TId>;
15
+ constructor(store: RoleStore<TId>);
16
16
  /**
17
- * Initializes a new instance of the RoleService.
17
+ * Retrieves all roles that grant a specific permission.
18
18
  *
19
- * @param store - Persistence layer for role definitions.
20
- * @param options - Identity system configuration options.
19
+ * @param permission - The permission string to search for.
20
+ * @returns A promise resolving to an array of roles.
21
21
  */
22
- constructor(store: RoleStore, options: IdentityOptions);
22
+ findByPermission(permission: string): Promise<IdentityRole[]>;
23
23
  /**
24
- * Persists a new security role definition.
24
+ * Retrieves a role by its hierarchical rank.
25
25
  *
26
- * @param role - The complete role definition to create.
27
- * @returns A promise that resolves when the role is saved.
26
+ * @param rank - The numeric rank to search for.
27
+ * @returns A promise resolving to the role or null if not found.
28
28
  */
29
- create(role: IdentityRole): Promise<void>;
29
+ findByRank(rank: number): Promise<IdentityRole | null>;
30
30
  /**
31
- * Updates an existing role's rank or permissions.
31
+ * Checks if a role is higher or equal than another based on rank.
32
32
  *
33
- * @param id - The unique technical identifier of the role to update.
34
- * @param data - Partial object containing the fields to modify.
35
- * @returns A promise that resolves when the update is complete.
33
+ * @param roleId - The role to check.
34
+ * @param requiredRoleId - The required role.
35
+ * @returns True if roleId has equal or higher rank.
36
36
  */
37
- update(id: string | number, data: Partial<Omit<IdentityRole, "id">>): Promise<void>;
38
- /**
39
- * Permanently removes a role definition from the system.
40
- *
41
- * @param id - The technical identifier of the role to delete.
42
- * @returns A promise that resolves when the role is deleted.
43
- */
44
- delete(id: string | number): Promise<void>;
45
- /**
46
- * Retrieves the full list of permissions granted to a specific role.
47
- *
48
- * @param id - The technical identifier of the role.
49
- * @returns A promise resolving to an array of permission strings.
50
- */
51
- getPermissions(id: string | number): Promise<string[]>;
37
+ isHigherOrEqual(roleId: TId, requiredRoleId: TId): Promise<boolean>;
52
38
  }
@@ -7,11 +7,7 @@ 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";
14
- import { IDENTITY_OPTIONS } from "../tokens";
10
+ import { injectable } from "tsyringe";
15
11
  import { RoleStore } from "../contracts";
16
12
  /**
17
13
  * High-level service for managing security roles and their associated permissions.
@@ -24,64 +20,46 @@ import { RoleStore } from "../contracts";
24
20
  * @injectable
25
21
  */
26
22
  let RoleService = class RoleService {
27
- /**
28
- * Initializes a new instance of the RoleService.
29
- *
30
- * @param store - Persistence layer for role definitions.
31
- * @param options - Identity system configuration options.
32
- */
33
- constructor(store, options) {
23
+ constructor(store) {
34
24
  this.store = store;
35
- this.options = options;
36
- }
37
- /**
38
- * Persists a new security role definition.
39
- *
40
- * @param role - The complete role definition to create.
41
- * @returns A promise that resolves when the role is saved.
42
- */
43
- async create(role) {
44
- await this.store.save(role);
45
25
  }
46
26
  /**
47
- * Updates an existing role's rank or permissions.
27
+ * Retrieves all roles that grant a specific permission.
48
28
  *
49
- * @param id - The unique technical identifier of the role to update.
50
- * @param data - Partial object containing the fields to modify.
51
- * @returns A promise that resolves when the update is complete.
29
+ * @param permission - The permission string to search for.
30
+ * @returns A promise resolving to an array of roles.
52
31
  */
53
- async update(id, data) {
54
- const existing = await this.store.findById(id);
55
- if (!existing)
56
- return;
57
- await this.store.save({
58
- ...existing,
59
- ...data,
60
- });
32
+ async findByPermission(permission) {
33
+ return this.store.findByPermission(permission);
61
34
  }
62
35
  /**
63
- * Permanently removes a role definition from the system.
36
+ * Retrieves a role by its hierarchical rank.
64
37
  *
65
- * @param id - The technical identifier of the role to delete.
66
- * @returns A promise that resolves when the role is deleted.
38
+ * @param rank - The numeric rank to search for.
39
+ * @returns A promise resolving to the role or null if not found.
67
40
  */
68
- async delete(id) {
69
- await this.store.delete(id);
41
+ async findByRank(rank) {
42
+ return this.store.findByRank(rank);
70
43
  }
71
44
  /**
72
- * Retrieves the full list of permissions granted to a specific role.
45
+ * Checks if a role is higher or equal than another based on rank.
73
46
  *
74
- * @param id - The technical identifier of the role.
75
- * @returns A promise resolving to an array of permission strings.
47
+ * @param roleId - The role to check.
48
+ * @param requiredRoleId - The required role.
49
+ * @returns True if roleId has equal or higher rank.
76
50
  */
77
- async getPermissions(id) {
78
- const role = await this.store.findById(id);
79
- 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;
80
59
  }
81
60
  };
82
61
  RoleService = __decorate([
83
62
  injectable(),
84
- __param(1, inject(IDENTITY_OPTIONS)),
85
- __metadata("design:paramtypes", [RoleStore, Object])
63
+ __metadata("design:paramtypes", [RoleStore])
86
64
  ], RoleService);
87
65
  export { RoleService };
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.4",
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",