@open-core/identity 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +682 -0
- package/dist/entities/account.entity.d.ts +34 -0
- package/dist/entities/account.entity.js +2 -0
- package/dist/entities/role.entity.d.ts +35 -0
- package/dist/entities/role.entity.js +2 -0
- package/dist/events/identity.events.d.ts +24 -0
- package/dist/events/identity.events.js +2 -0
- package/dist/index.d.ts +70 -0
- package/dist/index.js +100 -0
- package/dist/repositories/account.repository.d.ts +60 -0
- package/dist/repositories/account.repository.js +185 -0
- package/dist/repositories/role.repository.d.ts +50 -0
- package/dist/repositories/role.repository.js +79 -0
- package/dist/services/account.service.d.ts +78 -0
- package/dist/services/account.service.js +207 -0
- package/dist/services/auth/api-auth.provider.d.ts +30 -0
- package/dist/services/auth/api-auth.provider.js +134 -0
- package/dist/services/auth/credentials-auth.provider.d.ts +27 -0
- package/dist/services/auth/credentials-auth.provider.js +214 -0
- package/dist/services/auth/local-auth.provider.d.ts +28 -0
- package/dist/services/auth/local-auth.provider.js +135 -0
- package/dist/services/cache/memory-cache.service.d.ts +47 -0
- package/dist/services/cache/memory-cache.service.js +108 -0
- package/dist/services/identity-auth.provider.d.ts +18 -0
- package/dist/services/identity-auth.provider.js +125 -0
- package/dist/services/identity-principal.provider.d.ts +29 -0
- package/dist/services/identity-principal.provider.js +104 -0
- package/dist/services/principal/api-principal.provider.d.ts +27 -0
- package/dist/services/principal/api-principal.provider.js +141 -0
- package/dist/services/principal/local-principal.provider.d.ts +39 -0
- package/dist/services/principal/local-principal.provider.js +114 -0
- package/dist/services/role.service.d.ts +73 -0
- package/dist/services/role.service.js +145 -0
- package/dist/setup.d.ts +58 -0
- package/dist/setup.js +93 -0
- package/dist/types/auth.types.d.ts +48 -0
- package/dist/types/auth.types.js +2 -0
- package/dist/types/index.d.ts +36 -0
- package/dist/types/index.js +2 -0
- package/migrations/001_accounts_table.sql +16 -0
- package/migrations/002_roles_table.sql +21 -0
- package/migrations/003_alter_accounts_add_role.sql +24 -0
- package/migrations/004_rename_uuid_to_linked_id.sql +12 -0
- package/migrations/005_add_password_hash.sql +7 -0
- package/package.json +59 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
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;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ApiPrincipalProvider = void 0;
|
|
13
|
+
const tsyringe_1 = require("tsyringe");
|
|
14
|
+
const framework_1 = require("@open-core/framework");
|
|
15
|
+
const memory_cache_service_1 = require("../cache/memory-cache.service");
|
|
16
|
+
/**
|
|
17
|
+
* API-based principal provider that fetches permissions from external API.
|
|
18
|
+
* Does NOT require local database (uses memory cache only).
|
|
19
|
+
*
|
|
20
|
+
* Features:
|
|
21
|
+
* - GETs principal data from external API by linkedId
|
|
22
|
+
* - Caches results in RAM with configurable TTL
|
|
23
|
+
* - Falls back to empty permissions if API fails
|
|
24
|
+
* - Optionally syncs to local DB if configured
|
|
25
|
+
*
|
|
26
|
+
* Expected API endpoint: GET {principalUrl}/principals/{linkedId}
|
|
27
|
+
* Response: { name?: string, rank?: number, permissions: string[], meta?: {...} }
|
|
28
|
+
*/
|
|
29
|
+
let ApiPrincipalProvider = class ApiPrincipalProvider {
|
|
30
|
+
constructor(config, http, cache) {
|
|
31
|
+
this.config = config;
|
|
32
|
+
this.http = http;
|
|
33
|
+
this.cache = cache;
|
|
34
|
+
// Load API configuration from convars
|
|
35
|
+
const principalUrl = this.config.get("identity_api_principal_url", "") ||
|
|
36
|
+
this.config.get("identity_api_auth_url", "") ||
|
|
37
|
+
"http://localhost:3000/api";
|
|
38
|
+
this.apiConfig = {
|
|
39
|
+
authUrl: principalUrl,
|
|
40
|
+
principalUrl: principalUrl,
|
|
41
|
+
headers: this.parseHeaders(this.config.get("identity_api_headers", "")),
|
|
42
|
+
timeoutMs: this.config.getNumber("identity_api_timeout", 5000),
|
|
43
|
+
};
|
|
44
|
+
this.cacheTtl = this.config.getNumber("identity_cache_ttl", 300000); // 5 min default
|
|
45
|
+
}
|
|
46
|
+
async getPrincipal(player) {
|
|
47
|
+
const linked = player.accountID;
|
|
48
|
+
if (!linked) {
|
|
49
|
+
throw new framework_1.Utils.AppError("UNAUTHORIZED", "Player is not authenticated (no linked account)", "server");
|
|
50
|
+
}
|
|
51
|
+
// Check cache first
|
|
52
|
+
const cacheKey = `principal:${linked}`;
|
|
53
|
+
const cached = this.cache.get(cacheKey);
|
|
54
|
+
if (cached) {
|
|
55
|
+
return cached;
|
|
56
|
+
}
|
|
57
|
+
// Fetch from API
|
|
58
|
+
try {
|
|
59
|
+
const url = `${this.apiConfig.principalUrl}/principals/${linked}`;
|
|
60
|
+
const response = await this.http.get(url, {
|
|
61
|
+
headers: this.apiConfig.headers,
|
|
62
|
+
timeoutMs: this.apiConfig.timeoutMs,
|
|
63
|
+
});
|
|
64
|
+
const principal = {
|
|
65
|
+
id: String(linked),
|
|
66
|
+
name: response.name,
|
|
67
|
+
rank: response.rank,
|
|
68
|
+
permissions: response.permissions ?? [],
|
|
69
|
+
meta: response.meta ?? {},
|
|
70
|
+
};
|
|
71
|
+
// Cache the result
|
|
72
|
+
this.cache.set(cacheKey, principal, this.cacheTtl);
|
|
73
|
+
return principal;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
// If API fails, return empty principal or throw based on config
|
|
77
|
+
const allowFallback = this.config.getBoolean("identity_api_allow_fallback", false);
|
|
78
|
+
if (allowFallback) {
|
|
79
|
+
return {
|
|
80
|
+
id: String(linked),
|
|
81
|
+
permissions: [],
|
|
82
|
+
meta: {},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
throw new framework_1.Utils.AppError("UNAUTHORIZED", `Failed to fetch principal from API: ${error instanceof Error ? error.message : "Unknown error"}`, "server");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async refreshPrincipal(player) {
|
|
89
|
+
const linked = player.accountID;
|
|
90
|
+
if (linked) {
|
|
91
|
+
// Clear cache to force refresh
|
|
92
|
+
this.cache.delete(`principal:${linked}`);
|
|
93
|
+
}
|
|
94
|
+
const principal = await this.getPrincipal(player);
|
|
95
|
+
player.setMeta("identity:principal", principal);
|
|
96
|
+
}
|
|
97
|
+
async getPrincipalByLinkedID(linkedID) {
|
|
98
|
+
// Check cache first
|
|
99
|
+
const cacheKey = `principal:${linkedID}`;
|
|
100
|
+
const cached = this.cache.get(cacheKey);
|
|
101
|
+
if (cached) {
|
|
102
|
+
return cached;
|
|
103
|
+
}
|
|
104
|
+
// Fetch from API
|
|
105
|
+
try {
|
|
106
|
+
const url = `${this.apiConfig.principalUrl}/principals/${linkedID}`;
|
|
107
|
+
const response = await this.http.get(url, {
|
|
108
|
+
headers: this.apiConfig.headers,
|
|
109
|
+
timeoutMs: this.apiConfig.timeoutMs,
|
|
110
|
+
});
|
|
111
|
+
const principal = {
|
|
112
|
+
id: linkedID,
|
|
113
|
+
name: response.name,
|
|
114
|
+
rank: response.rank,
|
|
115
|
+
permissions: response.permissions ?? [],
|
|
116
|
+
meta: response.meta ?? {},
|
|
117
|
+
};
|
|
118
|
+
// Cache the result
|
|
119
|
+
this.cache.set(cacheKey, principal, this.cacheTtl);
|
|
120
|
+
return principal;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
parseHeaders(headersString) {
|
|
127
|
+
if (!headersString)
|
|
128
|
+
return {};
|
|
129
|
+
try {
|
|
130
|
+
return JSON.parse(headersString);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return {};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
exports.ApiPrincipalProvider = ApiPrincipalProvider;
|
|
138
|
+
exports.ApiPrincipalProvider = ApiPrincipalProvider = __decorate([
|
|
139
|
+
(0, tsyringe_1.injectable)(),
|
|
140
|
+
__metadata("design:paramtypes", [framework_1.Server.ConfigService, framework_1.Server.HttpService, memory_cache_service_1.MemoryCacheService])
|
|
141
|
+
], ApiPrincipalProvider);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Server } from "@open-core/framework";
|
|
2
|
+
import { AccountService } from "../account.service";
|
|
3
|
+
import { AccountRepository } from "../../repositories/account.repository";
|
|
4
|
+
/**
|
|
5
|
+
* Local principal provider that reads roles and permissions from local database.
|
|
6
|
+
* This is the default/traditional principal provider for FiveM servers.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Reads from local database (accounts + roles tables)
|
|
10
|
+
* - Combines role permissions with custom account permissions
|
|
11
|
+
* - Supports permission negation (e.g., "-admin.ban")
|
|
12
|
+
* - Caches in player metadata
|
|
13
|
+
*/
|
|
14
|
+
export declare class LocalPrincipalProvider implements Server.PrincipalProviderContract {
|
|
15
|
+
private readonly accounts;
|
|
16
|
+
private readonly repo;
|
|
17
|
+
constructor(accounts: AccountService, repo: AccountRepository);
|
|
18
|
+
getPrincipal(player: Server.Player): Promise<Server.Principal | null>;
|
|
19
|
+
refreshPrincipal(player: Server.Player): Promise<void>;
|
|
20
|
+
getPrincipalByLinkedID(linkedID: string): Promise<Server.Principal | null>;
|
|
21
|
+
/**
|
|
22
|
+
* Builds a Principal from account and role.
|
|
23
|
+
* Combines role permissions with account custom permissions.
|
|
24
|
+
*
|
|
25
|
+
* @param account - Account entity
|
|
26
|
+
* @param role - Role entity (or null if no role assigned)
|
|
27
|
+
* @returns Principal with combined permissions
|
|
28
|
+
*/
|
|
29
|
+
private toPrincipal;
|
|
30
|
+
/**
|
|
31
|
+
* Combine role permissions with account custom permissions.
|
|
32
|
+
* Custom permissions starting with '-' negate the base permission.
|
|
33
|
+
*
|
|
34
|
+
* @param role - Role with base permissions
|
|
35
|
+
* @param customPerms - Account custom permissions
|
|
36
|
+
* @returns Combined permissions array
|
|
37
|
+
*/
|
|
38
|
+
private combinePermissions;
|
|
39
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
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;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.LocalPrincipalProvider = void 0;
|
|
13
|
+
const tsyringe_1 = require("tsyringe");
|
|
14
|
+
const framework_1 = require("@open-core/framework");
|
|
15
|
+
const account_service_1 = require("../account.service");
|
|
16
|
+
const account_repository_1 = require("../../repositories/account.repository");
|
|
17
|
+
/**
|
|
18
|
+
* Local principal provider that reads roles and permissions from local database.
|
|
19
|
+
* This is the default/traditional principal provider for FiveM servers.
|
|
20
|
+
*
|
|
21
|
+
* Features:
|
|
22
|
+
* - Reads from local database (accounts + roles tables)
|
|
23
|
+
* - Combines role permissions with custom account permissions
|
|
24
|
+
* - Supports permission negation (e.g., "-admin.ban")
|
|
25
|
+
* - Caches in player metadata
|
|
26
|
+
*/
|
|
27
|
+
let LocalPrincipalProvider = class LocalPrincipalProvider {
|
|
28
|
+
constructor(accounts, repo) {
|
|
29
|
+
this.accounts = accounts;
|
|
30
|
+
this.repo = repo;
|
|
31
|
+
}
|
|
32
|
+
async getPrincipal(player) {
|
|
33
|
+
const linked = player.accountID;
|
|
34
|
+
if (!linked) {
|
|
35
|
+
throw new framework_1.Utils.AppError("UNAUTHORIZED", "Player is not authenticated (no linked account)", "server");
|
|
36
|
+
}
|
|
37
|
+
const result = await this.repo.findByLinkedIdWithRole(String(linked));
|
|
38
|
+
if (!result) {
|
|
39
|
+
throw new framework_1.Utils.AppError("UNAUTHORIZED", "Linked account not found", "server");
|
|
40
|
+
}
|
|
41
|
+
const { account, role } = result;
|
|
42
|
+
if (this.accounts.isBanExpired(account)) {
|
|
43
|
+
await this.accounts.unban(account.id);
|
|
44
|
+
account.banned = false;
|
|
45
|
+
}
|
|
46
|
+
if (account.banned) {
|
|
47
|
+
throw new framework_1.Utils.AppError("PERMISSION_DENIED", "Account is banned", "server", {
|
|
48
|
+
banReason: account.banReason,
|
|
49
|
+
banExpires: account.banExpires,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return this.toPrincipal(account, role);
|
|
53
|
+
}
|
|
54
|
+
async refreshPrincipal(player) {
|
|
55
|
+
const principal = await this.getPrincipal(player);
|
|
56
|
+
player.setMeta("identity:principal", principal);
|
|
57
|
+
}
|
|
58
|
+
async getPrincipalByLinkedID(linkedID) {
|
|
59
|
+
const result = await this.repo.findByLinkedIdWithRole(linkedID);
|
|
60
|
+
if (!result || result.account.banned)
|
|
61
|
+
return null;
|
|
62
|
+
return this.toPrincipal(result.account, result.role);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Builds a Principal from account and role.
|
|
66
|
+
* Combines role permissions with account custom permissions.
|
|
67
|
+
*
|
|
68
|
+
* @param account - Account entity
|
|
69
|
+
* @param role - Role entity (or null if no role assigned)
|
|
70
|
+
* @returns Principal with combined permissions
|
|
71
|
+
*/
|
|
72
|
+
toPrincipal(account, role) {
|
|
73
|
+
const effectivePermissions = this.combinePermissions(role, account.customPermissions);
|
|
74
|
+
return {
|
|
75
|
+
id: account.linkedId ?? String(account.id),
|
|
76
|
+
name: role?.displayName ?? undefined,
|
|
77
|
+
rank: role?.rank ?? undefined,
|
|
78
|
+
permissions: effectivePermissions,
|
|
79
|
+
meta: {
|
|
80
|
+
accountId: account.id,
|
|
81
|
+
roleId: role?.id,
|
|
82
|
+
roleName: role?.name,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Combine role permissions with account custom permissions.
|
|
88
|
+
* Custom permissions starting with '-' negate the base permission.
|
|
89
|
+
*
|
|
90
|
+
* @param role - Role with base permissions
|
|
91
|
+
* @param customPerms - Account custom permissions
|
|
92
|
+
* @returns Combined permissions array
|
|
93
|
+
*/
|
|
94
|
+
combinePermissions(role, customPerms) {
|
|
95
|
+
const base = new Set(role?.permissions ?? []);
|
|
96
|
+
for (const perm of customPerms) {
|
|
97
|
+
if (perm.startsWith("-")) {
|
|
98
|
+
// Negation: remove the base permission
|
|
99
|
+
base.delete(perm.slice(1));
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
// Addition: add custom permission
|
|
103
|
+
base.add(perm);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return Array.from(base);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
exports.LocalPrincipalProvider = LocalPrincipalProvider;
|
|
110
|
+
exports.LocalPrincipalProvider = LocalPrincipalProvider = __decorate([
|
|
111
|
+
(0, tsyringe_1.injectable)(),
|
|
112
|
+
__metadata("design:paramtypes", [account_service_1.AccountService,
|
|
113
|
+
account_repository_1.AccountRepository])
|
|
114
|
+
], LocalPrincipalProvider);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Role } from "../entities/role.entity";
|
|
2
|
+
import type { CreateRoleInput, UpdateRoleInput } from "../types";
|
|
3
|
+
import { RoleRepository } from "../repositories/role.repository";
|
|
4
|
+
/**
|
|
5
|
+
* Service for managing roles and their permissions.
|
|
6
|
+
* Handles CRUD operations and permission management for roles.
|
|
7
|
+
*/
|
|
8
|
+
export declare class RoleService {
|
|
9
|
+
private readonly repo;
|
|
10
|
+
constructor(repo: RoleRepository);
|
|
11
|
+
/**
|
|
12
|
+
* Find a role by its ID.
|
|
13
|
+
*
|
|
14
|
+
* @param id - Role ID
|
|
15
|
+
* @returns The role or null if not found
|
|
16
|
+
*/
|
|
17
|
+
findById(id: number): Promise<Role | null>;
|
|
18
|
+
/**
|
|
19
|
+
* Find a role by its internal name.
|
|
20
|
+
*
|
|
21
|
+
* @param name - Role name (e.g., 'admin', 'user')
|
|
22
|
+
* @returns The role or null if not found
|
|
23
|
+
*/
|
|
24
|
+
findByName(name: string): Promise<Role | null>;
|
|
25
|
+
/**
|
|
26
|
+
* Get all roles.
|
|
27
|
+
*
|
|
28
|
+
* @returns Array of all roles
|
|
29
|
+
*/
|
|
30
|
+
getAll(): Promise<Role[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Get the default role for new accounts.
|
|
33
|
+
*
|
|
34
|
+
* @returns The default role or null if none is configured
|
|
35
|
+
*/
|
|
36
|
+
getDefaultRole(): Promise<Role | null>;
|
|
37
|
+
/**
|
|
38
|
+
* Create a new role.
|
|
39
|
+
*
|
|
40
|
+
* @param input - Role creation data
|
|
41
|
+
* @returns The created role
|
|
42
|
+
*/
|
|
43
|
+
create(input: CreateRoleInput): Promise<Role>;
|
|
44
|
+
/**
|
|
45
|
+
* Update an existing role.
|
|
46
|
+
*
|
|
47
|
+
* @param id - Role ID
|
|
48
|
+
* @param input - Update data
|
|
49
|
+
* @returns The updated role or null if not found
|
|
50
|
+
*/
|
|
51
|
+
update(id: number, input: UpdateRoleInput): Promise<Role | null>;
|
|
52
|
+
/**
|
|
53
|
+
* Delete a role.
|
|
54
|
+
*
|
|
55
|
+
* @param id - Role ID
|
|
56
|
+
* @returns true if deleted, false if not found
|
|
57
|
+
*/
|
|
58
|
+
delete(id: number): Promise<boolean>;
|
|
59
|
+
/**
|
|
60
|
+
* Add a permission to a role.
|
|
61
|
+
*
|
|
62
|
+
* @param roleId - Role ID
|
|
63
|
+
* @param permission - Permission string to add
|
|
64
|
+
*/
|
|
65
|
+
addPermission(roleId: number, permission: string): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Remove a permission from a role.
|
|
68
|
+
*
|
|
69
|
+
* @param roleId - Role ID
|
|
70
|
+
* @param permission - Permission string to remove
|
|
71
|
+
*/
|
|
72
|
+
removePermission(roleId: number, permission: string): Promise<void>;
|
|
73
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
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;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.RoleService = void 0;
|
|
13
|
+
const tsyringe_1 = require("tsyringe");
|
|
14
|
+
const role_repository_1 = require("../repositories/role.repository");
|
|
15
|
+
/**
|
|
16
|
+
* Service for managing roles and their permissions.
|
|
17
|
+
* Handles CRUD operations and permission management for roles.
|
|
18
|
+
*/
|
|
19
|
+
let RoleService = class RoleService {
|
|
20
|
+
constructor(repo) {
|
|
21
|
+
this.repo = repo;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Find a role by its ID.
|
|
25
|
+
*
|
|
26
|
+
* @param id - Role ID
|
|
27
|
+
* @returns The role or null if not found
|
|
28
|
+
*/
|
|
29
|
+
async findById(id) {
|
|
30
|
+
return this.repo.findById(id);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Find a role by its internal name.
|
|
34
|
+
*
|
|
35
|
+
* @param name - Role name (e.g., 'admin', 'user')
|
|
36
|
+
* @returns The role or null if not found
|
|
37
|
+
*/
|
|
38
|
+
async findByName(name) {
|
|
39
|
+
return this.repo.findByName(name);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get all roles.
|
|
43
|
+
*
|
|
44
|
+
* @returns Array of all roles
|
|
45
|
+
*/
|
|
46
|
+
async getAll() {
|
|
47
|
+
const result = await this.repo.findMany();
|
|
48
|
+
return result.data;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get the default role for new accounts.
|
|
52
|
+
*
|
|
53
|
+
* @returns The default role or null if none is configured
|
|
54
|
+
*/
|
|
55
|
+
async getDefaultRole() {
|
|
56
|
+
return this.repo.getDefaultRole();
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Create a new role.
|
|
60
|
+
*
|
|
61
|
+
* @param input - Role creation data
|
|
62
|
+
* @returns The created role
|
|
63
|
+
*/
|
|
64
|
+
async create(input) {
|
|
65
|
+
const now = new Date();
|
|
66
|
+
const role = {
|
|
67
|
+
id: 0, // Will be set by DB
|
|
68
|
+
name: input.name,
|
|
69
|
+
displayName: input.displayName,
|
|
70
|
+
rank: input.rank,
|
|
71
|
+
permissions: input.permissions ?? [],
|
|
72
|
+
isDefault: input.isDefault ?? false,
|
|
73
|
+
createdAt: now,
|
|
74
|
+
};
|
|
75
|
+
return this.repo.save(role);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Update an existing role.
|
|
79
|
+
*
|
|
80
|
+
* @param id - Role ID
|
|
81
|
+
* @param input - Update data
|
|
82
|
+
* @returns The updated role or null if not found
|
|
83
|
+
*/
|
|
84
|
+
async update(id, input) {
|
|
85
|
+
const existing = await this.repo.findById(id);
|
|
86
|
+
if (!existing)
|
|
87
|
+
return null;
|
|
88
|
+
const updated = {
|
|
89
|
+
...existing,
|
|
90
|
+
...(input.displayName !== undefined && {
|
|
91
|
+
displayName: input.displayName,
|
|
92
|
+
}),
|
|
93
|
+
...(input.rank !== undefined && { rank: input.rank }),
|
|
94
|
+
...(input.permissions !== undefined && {
|
|
95
|
+
permissions: input.permissions,
|
|
96
|
+
}),
|
|
97
|
+
};
|
|
98
|
+
if (input.isDefault !== undefined) {
|
|
99
|
+
await this.repo.setDefault(id, input.isDefault);
|
|
100
|
+
updated.isDefault = input.isDefault;
|
|
101
|
+
}
|
|
102
|
+
return this.repo.save(updated);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Delete a role.
|
|
106
|
+
*
|
|
107
|
+
* @param id - Role ID
|
|
108
|
+
* @returns true if deleted, false if not found
|
|
109
|
+
*/
|
|
110
|
+
async delete(id) {
|
|
111
|
+
return this.repo.delete(id);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Add a permission to a role.
|
|
115
|
+
*
|
|
116
|
+
* @param roleId - Role ID
|
|
117
|
+
* @param permission - Permission string to add
|
|
118
|
+
*/
|
|
119
|
+
async addPermission(roleId, permission) {
|
|
120
|
+
const role = await this.repo.findById(roleId);
|
|
121
|
+
if (!role)
|
|
122
|
+
return;
|
|
123
|
+
const permissions = new Set(role.permissions);
|
|
124
|
+
permissions.add(permission);
|
|
125
|
+
await this.repo.updatePermissions(roleId, Array.from(permissions));
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Remove a permission from a role.
|
|
129
|
+
*
|
|
130
|
+
* @param roleId - Role ID
|
|
131
|
+
* @param permission - Permission string to remove
|
|
132
|
+
*/
|
|
133
|
+
async removePermission(roleId, permission) {
|
|
134
|
+
const role = await this.repo.findById(roleId);
|
|
135
|
+
if (!role)
|
|
136
|
+
return;
|
|
137
|
+
const filtered = role.permissions.filter((p) => p !== permission);
|
|
138
|
+
await this.repo.updatePermissions(roleId, filtered);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
exports.RoleService = RoleService;
|
|
142
|
+
exports.RoleService = RoleService = __decorate([
|
|
143
|
+
(0, tsyringe_1.injectable)(),
|
|
144
|
+
__metadata("design:paramtypes", [role_repository_1.RoleRepository])
|
|
145
|
+
], RoleService);
|
package/dist/setup.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { DependencyContainer } from "tsyringe";
|
|
2
|
+
/**
|
|
3
|
+
* Configuration options for Identity module setup
|
|
4
|
+
*/
|
|
5
|
+
export interface IdentitySetupOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Authentication provider strategy
|
|
8
|
+
* - 'local': Auto-create accounts by FiveM identifiers (default)
|
|
9
|
+
* - 'credentials': Username/password authentication
|
|
10
|
+
* - 'api': External API authentication
|
|
11
|
+
*/
|
|
12
|
+
authProvider?: "local" | "credentials" | "api";
|
|
13
|
+
/**
|
|
14
|
+
* Principal provider strategy
|
|
15
|
+
* - 'local': Read roles/permissions from local DB (default)
|
|
16
|
+
* - 'api': Fetch roles/permissions from external API
|
|
17
|
+
*/
|
|
18
|
+
principalProvider?: "local" | "api";
|
|
19
|
+
/**
|
|
20
|
+
* Whether to use local database for accounts/roles
|
|
21
|
+
* - true: Use DB (default for 'local' strategies)
|
|
22
|
+
* - false: Skip DB registration (only cache, for 'api' strategies)
|
|
23
|
+
*/
|
|
24
|
+
useDatabase?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Register all Identity module singletons with the DI container.
|
|
28
|
+
*
|
|
29
|
+
* This function should be called once during server bootstrap to set up
|
|
30
|
+
* the authentication and authorization providers.
|
|
31
|
+
*
|
|
32
|
+
* @param container - The tsyringe DependencyContainer instance
|
|
33
|
+
* @param options - Configuration options for authentication and principal strategies
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* import { container } from "tsyringe";
|
|
38
|
+
* import { Identity } from "@open-core/identity";
|
|
39
|
+
*
|
|
40
|
+
* // Default setup (local auth + local principals + DB)
|
|
41
|
+
* Identity.setup(container);
|
|
42
|
+
*
|
|
43
|
+
* // API-based setup (no DB required)
|
|
44
|
+
* Identity.setup(container, {
|
|
45
|
+
* authProvider: 'api',
|
|
46
|
+
* principalProvider: 'api',
|
|
47
|
+
* useDatabase: false
|
|
48
|
+
* });
|
|
49
|
+
*
|
|
50
|
+
* // Credentials setup (DB required)
|
|
51
|
+
* Identity.setup(container, {
|
|
52
|
+
* authProvider: 'credentials',
|
|
53
|
+
* principalProvider: 'local',
|
|
54
|
+
* useDatabase: true
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare function setupIdentity(container: DependencyContainer, options?: IdentitySetupOptions): void;
|
package/dist/setup.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setupIdentity = setupIdentity;
|
|
4
|
+
const account_repository_1 = require("./repositories/account.repository");
|
|
5
|
+
const role_repository_1 = require("./repositories/role.repository");
|
|
6
|
+
const account_service_1 = require("./services/account.service");
|
|
7
|
+
const role_service_1 = require("./services/role.service");
|
|
8
|
+
const memory_cache_service_1 = require("./services/cache/memory-cache.service");
|
|
9
|
+
const local_auth_provider_1 = require("./services/auth/local-auth.provider");
|
|
10
|
+
const credentials_auth_provider_1 = require("./services/auth/credentials-auth.provider");
|
|
11
|
+
const api_auth_provider_1 = require("./services/auth/api-auth.provider");
|
|
12
|
+
const local_principal_provider_1 = require("./services/principal/local-principal.provider");
|
|
13
|
+
const api_principal_provider_1 = require("./services/principal/api-principal.provider");
|
|
14
|
+
const identity_auth_provider_1 = require("./services/identity-auth.provider");
|
|
15
|
+
const identity_principal_provider_1 = require("./services/identity-principal.provider");
|
|
16
|
+
/**
|
|
17
|
+
* Register all Identity module singletons with the DI container.
|
|
18
|
+
*
|
|
19
|
+
* This function should be called once during server bootstrap to set up
|
|
20
|
+
* the authentication and authorization providers.
|
|
21
|
+
*
|
|
22
|
+
* @param container - The tsyringe DependencyContainer instance
|
|
23
|
+
* @param options - Configuration options for authentication and principal strategies
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* import { container } from "tsyringe";
|
|
28
|
+
* import { Identity } from "@open-core/identity";
|
|
29
|
+
*
|
|
30
|
+
* // Default setup (local auth + local principals + DB)
|
|
31
|
+
* Identity.setup(container);
|
|
32
|
+
*
|
|
33
|
+
* // API-based setup (no DB required)
|
|
34
|
+
* Identity.setup(container, {
|
|
35
|
+
* authProvider: 'api',
|
|
36
|
+
* principalProvider: 'api',
|
|
37
|
+
* useDatabase: false
|
|
38
|
+
* });
|
|
39
|
+
*
|
|
40
|
+
* // Credentials setup (DB required)
|
|
41
|
+
* Identity.setup(container, {
|
|
42
|
+
* authProvider: 'credentials',
|
|
43
|
+
* principalProvider: 'local',
|
|
44
|
+
* useDatabase: true
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
function setupIdentity(container, options = {}) {
|
|
49
|
+
const { authProvider = "local", principalProvider = "local", useDatabase = authProvider !== "api" && principalProvider !== "api", } = options;
|
|
50
|
+
// Always register cache service (needed for API providers)
|
|
51
|
+
container.registerSingleton(memory_cache_service_1.MemoryCacheService);
|
|
52
|
+
// Register database-dependent services only if needed
|
|
53
|
+
if (useDatabase) {
|
|
54
|
+
container.registerSingleton(account_repository_1.AccountRepository);
|
|
55
|
+
container.registerSingleton(role_repository_1.RoleRepository);
|
|
56
|
+
container.registerSingleton(account_service_1.AccountService);
|
|
57
|
+
container.registerSingleton(role_service_1.RoleService);
|
|
58
|
+
}
|
|
59
|
+
// Register Auth Provider based on strategy
|
|
60
|
+
switch (authProvider) {
|
|
61
|
+
case "credentials":
|
|
62
|
+
container.registerSingleton("AuthProviderContract", credentials_auth_provider_1.CredentialsAuthProvider);
|
|
63
|
+
// Also register as IdentityAuthProvider for backward compatibility
|
|
64
|
+
// Note: CredentialsAuthProvider is not compatible with IdentityAuthProvider interface
|
|
65
|
+
// Users should use the new provider directly
|
|
66
|
+
break;
|
|
67
|
+
case "api":
|
|
68
|
+
container.registerSingleton("AuthProviderContract", api_auth_provider_1.ApiAuthProvider);
|
|
69
|
+
// Note: ApiAuthProvider is not compatible with IdentityAuthProvider interface
|
|
70
|
+
// Users should use the new provider directly
|
|
71
|
+
break;
|
|
72
|
+
case "local":
|
|
73
|
+
default:
|
|
74
|
+
container.registerSingleton("AuthProviderContract", local_auth_provider_1.LocalAuthProvider);
|
|
75
|
+
// Register LocalAuthProvider as the legacy IdentityAuthProvider for compatibility
|
|
76
|
+
container.registerSingleton(identity_auth_provider_1.IdentityAuthProvider, local_auth_provider_1.LocalAuthProvider);
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
// Register Principal Provider based on strategy
|
|
80
|
+
switch (principalProvider) {
|
|
81
|
+
case "api":
|
|
82
|
+
container.registerSingleton("PrincipalProviderContract", api_principal_provider_1.ApiPrincipalProvider);
|
|
83
|
+
// Note: ApiPrincipalProvider is not compatible with IdentityPrincipalProvider interface
|
|
84
|
+
// Users should use the new provider directly
|
|
85
|
+
break;
|
|
86
|
+
case "local":
|
|
87
|
+
default:
|
|
88
|
+
container.registerSingleton("PrincipalProviderContract", local_principal_provider_1.LocalPrincipalProvider);
|
|
89
|
+
// Register LocalPrincipalProvider as the legacy IdentityPrincipalProvider for compatibility
|
|
90
|
+
container.registerSingleton(identity_principal_provider_1.IdentityPrincipalProvider, local_principal_provider_1.LocalPrincipalProvider);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|