@open-core/identity 1.2.6 → 1.3.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/README.md +171 -76
- package/dist/auth.service.d.ts +48 -0
- package/dist/auth.service.js +2 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +20 -8
- package/dist/providers/auth/api-auth.provider.d.ts +12 -11
- package/dist/providers/auth/api-auth.provider.js +88 -14
- package/dist/providers/auth/credentials-auth.provider.d.ts +9 -9
- package/dist/providers/auth/credentials-auth.provider.js +30 -16
- package/dist/providers/auth/local-auth.provider.d.ts +4 -31
- package/dist/providers/auth/local-auth.provider.js +35 -36
- package/dist/providers/principal/api-principal.provider.d.ts +6 -3
- package/dist/providers/principal/api-principal.provider.js +80 -7
- package/dist/providers/principal/local-principal.provider.d.ts +1 -1
- package/dist/providers/principal/local-principal.provider.js +12 -7
- package/dist/services/account.service.js +18 -11
- package/dist/services/role.service.d.ts +7 -0
- package/dist/services/role.service.js +9 -0
- package/dist/types.d.ts +31 -8
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -1,76 +1,171 @@
|
|
|
1
|
-
# @open-core/identity
|
|
2
|
-
|
|
3
|
-
Enterprise-grade identity, authentication, and authorization plugin for the OpenCore Framework.
|
|
4
|
-
|
|
5
|
-
## Documentation Index
|
|
6
|
-
|
|
7
|
-
- [Architecture & Dependency Injection](./docs/architecture.md) - Learn about constructor injection and DI.
|
|
8
|
-
- [Authentication Modes](./docs/auth-modes.md) - Details on `local`, `credentials`, and `api` auth.
|
|
9
|
-
- [Principal Modes](./docs/principal-modes.md) - Details on `roles`, `db`, and `api` authorization.
|
|
10
|
-
- [Implementing Contracts](./docs/contracts.md) - How to build your own `IdentityStore` or `RoleStore`.
|
|
11
|
-
|
|
12
|
-
## Features
|
|
13
|
-
|
|
14
|
-
- **Multi-Strategy Authentication**: Support for `local`, `credentials`, and `api` strategies.
|
|
15
|
-
- **Hierarchical RBAC**: Rank-based authorization and permission merging.
|
|
16
|
-
- **Constructor Injection**: Services are automatically available in your classes via DI.
|
|
17
|
-
- **Stateless Architecture**: Decoupled persistence via implementable contracts.
|
|
18
|
-
|
|
19
|
-
## Quick Start (Constructor Injection)
|
|
20
|
-
|
|
21
|
-
The recommended way to use the identity system is through **Constructor Injection**. The framework handles the lifecycle for you.
|
|
22
|
-
|
|
23
|
-
```ts
|
|
24
|
-
import { Server } from "@open-core/framework";
|
|
25
|
-
import { AccountService } from "@open-core/identity";
|
|
26
|
-
|
|
27
|
-
@Server.Controller()
|
|
28
|
-
export class MyController {
|
|
29
|
-
// AccountService is automatically injected
|
|
30
|
-
constructor(private readonly accounts: AccountService) {}
|
|
31
|
-
|
|
32
|
-
@Server.OnNet("admin:ban")
|
|
33
|
-
async handleBan(player: Server.Player, targetId: string) {
|
|
34
|
-
await this.accounts.ban(targetId, { reason: "Policy violation" });
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## Installation & Setup
|
|
40
|
-
|
|
41
|
-
1. **Implement your Store** (See [Contracts](./docs/contracts.md)):
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
1
|
+
# @open-core/identity
|
|
2
|
+
|
|
3
|
+
Enterprise-grade identity, authentication, and authorization plugin for the OpenCore Framework.
|
|
4
|
+
|
|
5
|
+
## Documentation Index
|
|
6
|
+
|
|
7
|
+
- [Architecture & Dependency Injection](./docs/architecture.md) - Learn about constructor injection and DI.
|
|
8
|
+
- [Authentication Modes](./docs/auth-modes.md) - Details on `local`, `credentials`, and `api` auth.
|
|
9
|
+
- [Principal Modes](./docs/principal-modes.md) - Details on `roles`, `db`, and `api` authorization.
|
|
10
|
+
- [Implementing Contracts](./docs/contracts.md) - How to build your own `IdentityStore` or `RoleStore`.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **Multi-Strategy Authentication**: Support for `local`, `credentials`, and `api` strategies.
|
|
15
|
+
- **Hierarchical RBAC**: Rank-based authorization and permission merging.
|
|
16
|
+
- **Constructor Injection**: Services are automatically available in your classes via DI.
|
|
17
|
+
- **Stateless Architecture**: Decoupled persistence via implementable contracts.
|
|
18
|
+
|
|
19
|
+
## Quick Start (Constructor Injection)
|
|
20
|
+
|
|
21
|
+
The recommended way to use the identity system is through **Constructor Injection**. The framework handles the lifecycle for you.
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { Server } from "@open-core/framework";
|
|
25
|
+
import { AccountService } from "@open-core/identity";
|
|
26
|
+
|
|
27
|
+
@Server.Controller()
|
|
28
|
+
export class MyController {
|
|
29
|
+
// AccountService is automatically injected
|
|
30
|
+
constructor(private readonly accounts: AccountService) {}
|
|
31
|
+
|
|
32
|
+
@Server.OnNet("admin:ban")
|
|
33
|
+
async handleBan(player: Server.Player, targetId: string) {
|
|
34
|
+
await this.accounts.ban(targetId, { reason: "Policy violation" });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Installation & Setup
|
|
40
|
+
|
|
41
|
+
1. **Implement your Store** (See [Contracts](./docs/contracts.md)):
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { Identity, IdentityStore } from "@open-core/identity";
|
|
45
|
+
|
|
46
|
+
class MyStore extends IdentityStore {
|
|
47
|
+
/* ... */
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Register it before installation
|
|
51
|
+
Identity.setIdentityStore(MyStore);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
2. **Install the Plugin**:
|
|
55
|
+
```ts
|
|
56
|
+
Identity.install({
|
|
57
|
+
auth: { mode: "local", autoCreate: true, primaryIdentifier: "license" },
|
|
58
|
+
principal: {
|
|
59
|
+
mode: "roles",
|
|
60
|
+
roles: {
|
|
61
|
+
admin: { name: "admin", rank: 100, permissions: ["*"] },
|
|
62
|
+
user: { name: "user", rank: 0, permissions: ["chat.use"] },
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Auth Strategies
|
|
69
|
+
|
|
70
|
+
All strategies are resolved through `AuthService`. Configure one mode and the framework
|
|
71
|
+
will inject the correct provider.
|
|
72
|
+
|
|
73
|
+
Quick summary:
|
|
74
|
+
|
|
75
|
+
- local: identifies by license/steam/discord, no form.
|
|
76
|
+
- credentials: username/password stored on your server.
|
|
77
|
+
- api: delegates to an external HTTP service.
|
|
78
|
+
|
|
79
|
+
### Local (Identifiers)
|
|
80
|
+
|
|
81
|
+
What it is: authenticates using player identifiers (license/steam/discord).
|
|
82
|
+
Who it is for: servers that want automatic access without forms.
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
Identity.install({
|
|
86
|
+
auth: {
|
|
87
|
+
mode: "local",
|
|
88
|
+
primaryIdentifier: "license",
|
|
89
|
+
autoCreate: true,
|
|
90
|
+
},
|
|
91
|
+
// ...
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Credentials (Username/Password)
|
|
96
|
+
|
|
97
|
+
What it is: login and registration with username/password stored on your server.
|
|
98
|
+
Who it is for: servers with custom UI or manual registration.
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
Identity.install({
|
|
102
|
+
auth: {
|
|
103
|
+
mode: "credentials",
|
|
104
|
+
},
|
|
105
|
+
// ...
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### API (External Service)
|
|
110
|
+
|
|
111
|
+
What it is: delegates all auth to an external HTTP service.
|
|
112
|
+
Who it is for: large networks with a centralized database or SSO.
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
Identity.install({
|
|
116
|
+
auth: {
|
|
117
|
+
mode: "api",
|
|
118
|
+
primaryIdentifier: "license",
|
|
119
|
+
api: {
|
|
120
|
+
baseUrl: "https://auth.example.com",
|
|
121
|
+
authPath: "/auth",
|
|
122
|
+
registerPath: "/register",
|
|
123
|
+
sessionPath: "/session",
|
|
124
|
+
logoutPath: "/logout",
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
// ...
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Usage Example
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { Server } from "@open-core/framework";
|
|
135
|
+
import { AuthService } from "@open-core/identity";
|
|
136
|
+
|
|
137
|
+
@Server.Controller()
|
|
138
|
+
export class AuthController {
|
|
139
|
+
constructor(private readonly auth: AuthService) {}
|
|
140
|
+
|
|
141
|
+
@Server.OnNet("auth:login")
|
|
142
|
+
async login(
|
|
143
|
+
player: Server.Player,
|
|
144
|
+
payload: { username: string; password: string },
|
|
145
|
+
) {
|
|
146
|
+
return this.auth.authenticate(player, payload);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@Server.OnNet("auth:register")
|
|
150
|
+
async register(
|
|
151
|
+
player: Server.Player,
|
|
152
|
+
payload: { username: string; password: string },
|
|
153
|
+
) {
|
|
154
|
+
return this.auth.register(player, payload);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Exports
|
|
160
|
+
|
|
161
|
+
The library only exports high-level components to keep your IDE suggestions clean:
|
|
162
|
+
|
|
163
|
+
- `Identity`: The main namespace for installation and registration.
|
|
164
|
+
- `AccountService`, `RoleService`: Public services for business logic.
|
|
165
|
+
- `IdentityStore`, `RoleStore`: Abstract contracts for persistence.
|
|
166
|
+
- `IDENTITY_OPTIONS`: Token for advanced DI usage.
|
|
167
|
+
- All relevant types and interfaces.
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
MIT
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Server } from "@open-core/framework/server";
|
|
2
|
+
import { IdentityAccount } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Result structure for authentication operations.
|
|
5
|
+
*/
|
|
6
|
+
export interface AuthResult {
|
|
7
|
+
/** Indicates if the operation was successful */
|
|
8
|
+
success: boolean;
|
|
9
|
+
/** The unique identifier for the authenticated account */
|
|
10
|
+
accountID?: string;
|
|
11
|
+
/** Error message if the operation failed */
|
|
12
|
+
error?: string;
|
|
13
|
+
/** Indicates if a new account was created during the process */
|
|
14
|
+
isNewAccount?: boolean;
|
|
15
|
+
/** Generic account Data type */
|
|
16
|
+
account?: IdentityAccount;
|
|
17
|
+
}
|
|
18
|
+
export declare abstract class AuthService {
|
|
19
|
+
/**
|
|
20
|
+
* Authenticates a player using the selected strategy.
|
|
21
|
+
*
|
|
22
|
+
* @param player - The framework player entity.
|
|
23
|
+
* @param credentials - Strategy-specific credentials.
|
|
24
|
+
* @returns A promise resolving to the authentication result.
|
|
25
|
+
*/
|
|
26
|
+
abstract authenticate(player: Server.Player, credentials: Record<string, unknown>): Promise<AuthResult>;
|
|
27
|
+
/**
|
|
28
|
+
* Registers a new account for the player.
|
|
29
|
+
*
|
|
30
|
+
* @param player - The framework player entity.
|
|
31
|
+
* @param credentials - Strategy-specific registration data.
|
|
32
|
+
* @returns A promise resolving to the registration result.
|
|
33
|
+
*/
|
|
34
|
+
abstract register(player: Server.Player, credentials: Record<string, unknown>): Promise<AuthResult>;
|
|
35
|
+
/**
|
|
36
|
+
* Validates if the player's current linked account session is still active.
|
|
37
|
+
*
|
|
38
|
+
* @param player - The framework player entity.
|
|
39
|
+
* @returns A promise resolving to the validation result.
|
|
40
|
+
*/
|
|
41
|
+
abstract validateSession(player: Server.Player): Promise<AuthResult>;
|
|
42
|
+
/**
|
|
43
|
+
* Clears authentication state for the player.
|
|
44
|
+
*
|
|
45
|
+
* @param player - The framework player entity.
|
|
46
|
+
*/
|
|
47
|
+
abstract logout(player: Server.Player): Promise<void>;
|
|
48
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -37,8 +37,8 @@ export declare namespace Identity {
|
|
|
37
37
|
/**
|
|
38
38
|
* Installs the Identity plugin into the OpenCore Framework.
|
|
39
39
|
*
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
* This function registers the necessary Authentication service and Principal provider
|
|
41
|
+
* into the framework's SPI via dependency injection and `Server.setPrincipalProvider`.
|
|
42
42
|
*
|
|
43
43
|
* @param options - Configuration options for the identity system.
|
|
44
44
|
*
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { Server } from "@open-core/framework";
|
|
1
|
+
import { Server } from "@open-core/framework/server";
|
|
2
2
|
import { IDENTITY_OPTIONS } from "./tokens";
|
|
3
3
|
import { LocalAuthProvider as LocalAuthImpl } from "./providers/auth/local-auth.provider";
|
|
4
4
|
import { CredentialsAuthProvider as CredentialsAuthImpl } from "./providers/auth/credentials-auth.provider";
|
|
5
5
|
import { ApiAuthProvider as ApiAuthImpl } from "./providers/auth/api-auth.provider";
|
|
6
|
+
import { AuthService } from "./auth.service";
|
|
6
7
|
import { IdentityPrincipalProvider as PrincipalProviderImpl } from "./providers/principal/local-principal.provider";
|
|
7
8
|
import { ApiPrincipalProvider as ApiPrincipalImpl } from "./providers/principal/api-principal.provider";
|
|
8
9
|
import { AccountService as AccountServiceImpl } from "./services/account.service";
|
|
@@ -28,7 +29,10 @@ export var Identity;
|
|
|
28
29
|
const container = globalThis.oc_container;
|
|
29
30
|
if (!container)
|
|
30
31
|
throwContainerError();
|
|
32
|
+
// Unregister existing if any and register new singleton
|
|
33
|
+
container.unregister(IdentityStoreContract);
|
|
31
34
|
container.registerSingleton(IdentityStoreContract, store);
|
|
35
|
+
console.log(`[OpenCore-Identity] IdentityStore registered: ${store.name}`);
|
|
32
36
|
}
|
|
33
37
|
Identity.setIdentityStore = setIdentityStore;
|
|
34
38
|
/**
|
|
@@ -41,18 +45,20 @@ export var Identity;
|
|
|
41
45
|
const container = globalThis.oc_container;
|
|
42
46
|
if (!container)
|
|
43
47
|
throwContainerError();
|
|
48
|
+
container.unregister(RoleStoreContract);
|
|
44
49
|
container.registerSingleton(RoleStoreContract, store);
|
|
50
|
+
console.log(`[OpenCore-Identity] RoleStore registered: ${store.name}`);
|
|
45
51
|
}
|
|
46
52
|
Identity.setRoleStore = setRoleStore;
|
|
47
53
|
function throwContainerError() {
|
|
48
54
|
throw new Error("[OpenCore-Identity] Global container (globalThis.oc_container) not found. " +
|
|
49
|
-
"Ensure the framework is
|
|
55
|
+
"Ensure the framework is installed.");
|
|
50
56
|
}
|
|
51
57
|
/**
|
|
52
58
|
* Installs the Identity plugin into the OpenCore Framework.
|
|
53
59
|
*
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
* This function registers the necessary Authentication service and Principal provider
|
|
61
|
+
* into the framework's SPI via dependency injection and `Server.setPrincipalProvider`.
|
|
56
62
|
*
|
|
57
63
|
* @param options - Configuration options for the identity system.
|
|
58
64
|
*
|
|
@@ -80,18 +86,24 @@ export var Identity;
|
|
|
80
86
|
// Register Internal Services (concrete classes as tokens)
|
|
81
87
|
container.registerSingleton(AccountServiceImpl);
|
|
82
88
|
container.registerSingleton(RoleServiceImpl);
|
|
83
|
-
// Configure Auth
|
|
89
|
+
// Configure Auth Service based on mode
|
|
84
90
|
if (options.auth.mode === "api") {
|
|
85
|
-
|
|
91
|
+
if (!options.auth.api?.baseUrl) {
|
|
92
|
+
throw new Error("[OpenCore-Identity] In 'api' auth mode, 'auth.api.baseUrl' is required.");
|
|
93
|
+
}
|
|
94
|
+
container.registerSingleton(AuthService, ApiAuthImpl);
|
|
86
95
|
}
|
|
87
96
|
else if (options.auth.mode === "credentials") {
|
|
88
|
-
|
|
97
|
+
container.registerSingleton(AuthService, CredentialsAuthImpl);
|
|
89
98
|
}
|
|
90
99
|
else {
|
|
91
|
-
|
|
100
|
+
container.registerSingleton(AuthService, LocalAuthImpl);
|
|
92
101
|
}
|
|
93
102
|
// Configure Principal SPI based on mode
|
|
94
103
|
if (options.principal.mode === "api") {
|
|
104
|
+
if (!options.principal.api?.baseUrl) {
|
|
105
|
+
throw new Error("[OpenCore-Identity] In 'api' principal mode, 'principal.api.baseUrl' is required.");
|
|
106
|
+
}
|
|
95
107
|
Server.setPrincipalProvider(ApiPrincipalImpl);
|
|
96
108
|
if (options.principal.defaultRole && typeof options.principal.defaultRole !== "string") {
|
|
97
109
|
throw new Error("[OpenCore-Identity] In 'api' principal mode, 'defaultRole' must be a string (the ID returned by the API).");
|
|
@@ -1,25 +1,23 @@
|
|
|
1
|
-
import { Server } from "@open-core/framework";
|
|
1
|
+
import { Server } from "@open-core/framework/server";
|
|
2
2
|
import type { IdentityOptions } from "../../types";
|
|
3
|
+
import { AuthResult, AuthService } from "../../auth.service";
|
|
3
4
|
/**
|
|
4
5
|
* Authentication provider that delegates logic to an external HTTP API.
|
|
5
6
|
*
|
|
6
|
-
* This provider
|
|
7
|
-
*
|
|
8
|
-
* for environments with a centralized user database or SSO.
|
|
7
|
+
* This provider performs HTTP requests to a remote authentication service.
|
|
8
|
+
* It is suitable for environments with a centralized user database or SSO.
|
|
9
9
|
*
|
|
10
10
|
* @injectable
|
|
11
11
|
* @public
|
|
12
12
|
*/
|
|
13
|
-
export declare class ApiAuthProvider extends
|
|
13
|
+
export declare class ApiAuthProvider extends AuthService {
|
|
14
14
|
private readonly options;
|
|
15
|
-
private readonly http;
|
|
16
15
|
/**
|
|
17
16
|
* Initializes a new instance of the ApiAuthProvider.
|
|
18
17
|
*
|
|
19
18
|
* @param options - Identity system configuration options.
|
|
20
|
-
* @param http - Framework HTTP service for remote communication.
|
|
21
19
|
*/
|
|
22
|
-
constructor(options: IdentityOptions
|
|
20
|
+
constructor(options: IdentityOptions);
|
|
23
21
|
/**
|
|
24
22
|
* Authenticates a player by sending credentials to the external API.
|
|
25
23
|
*
|
|
@@ -27,7 +25,7 @@ export declare class ApiAuthProvider extends Server.AuthProviderContract {
|
|
|
27
25
|
* @param credentials - Authentication data (e.g., tokens, external IDs).
|
|
28
26
|
* @returns A promise resolving to the remote authentication result.
|
|
29
27
|
*/
|
|
30
|
-
authenticate(player: Server.Player, credentials: Record<string, unknown>): Promise<
|
|
28
|
+
authenticate(player: Server.Player, credentials: Record<string, unknown>): Promise<AuthResult>;
|
|
31
29
|
/**
|
|
32
30
|
* Registers a player identity via the external API.
|
|
33
31
|
*
|
|
@@ -35,18 +33,21 @@ export declare class ApiAuthProvider extends Server.AuthProviderContract {
|
|
|
35
33
|
* @param credentials - Registration data.
|
|
36
34
|
* @returns A promise resolving to the remote registration result.
|
|
37
35
|
*/
|
|
38
|
-
register(player: Server.Player, credentials: Record<string, unknown>): Promise<
|
|
36
|
+
register(player: Server.Player, credentials: Record<string, unknown>): Promise<AuthResult>;
|
|
39
37
|
/**
|
|
40
38
|
* Validates the player's remote session.
|
|
41
39
|
*
|
|
42
40
|
* @param player - The framework player entity.
|
|
43
41
|
* @returns A promise resolving to the remote session validation result.
|
|
44
42
|
*/
|
|
45
|
-
validateSession(player: Server.Player): Promise<
|
|
43
|
+
validateSession(player: Server.Player): Promise<AuthResult>;
|
|
46
44
|
/**
|
|
47
45
|
* Notifies the external API that the player has logged out.
|
|
48
46
|
*
|
|
49
47
|
* @param player - The framework player entity.
|
|
50
48
|
*/
|
|
51
49
|
logout(player: Server.Player): Promise<void>;
|
|
50
|
+
private requestAuth;
|
|
51
|
+
private resolveUrl;
|
|
52
|
+
private getAbortSignal;
|
|
52
53
|
}
|
|
@@ -11,29 +11,26 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
11
11
|
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
12
|
};
|
|
13
13
|
import { injectable, inject } from "tsyringe";
|
|
14
|
-
import { Server } from "@open-core/framework";
|
|
15
14
|
import { IDENTITY_OPTIONS } from "../../tokens";
|
|
15
|
+
import { AuthService } from "../../auth.service";
|
|
16
16
|
/**
|
|
17
17
|
* Authentication provider that delegates logic to an external HTTP API.
|
|
18
18
|
*
|
|
19
|
-
* This provider
|
|
20
|
-
*
|
|
21
|
-
* for environments with a centralized user database or SSO.
|
|
19
|
+
* This provider performs HTTP requests to a remote authentication service.
|
|
20
|
+
* It is suitable for environments with a centralized user database or SSO.
|
|
22
21
|
*
|
|
23
22
|
* @injectable
|
|
24
23
|
* @public
|
|
25
24
|
*/
|
|
26
|
-
let ApiAuthProvider = class ApiAuthProvider extends
|
|
25
|
+
let ApiAuthProvider = class ApiAuthProvider extends AuthService {
|
|
27
26
|
/**
|
|
28
27
|
* Initializes a new instance of the ApiAuthProvider.
|
|
29
28
|
*
|
|
30
29
|
* @param options - Identity system configuration options.
|
|
31
|
-
* @param http - Framework HTTP service for remote communication.
|
|
32
30
|
*/
|
|
33
|
-
constructor(options
|
|
31
|
+
constructor(options) {
|
|
34
32
|
super();
|
|
35
33
|
this.options = options;
|
|
36
|
-
this.http = http;
|
|
37
34
|
}
|
|
38
35
|
/**
|
|
39
36
|
* Authenticates a player by sending credentials to the external API.
|
|
@@ -43,8 +40,7 @@ let ApiAuthProvider = class ApiAuthProvider extends Server.AuthProviderContract
|
|
|
43
40
|
* @returns A promise resolving to the remote authentication result.
|
|
44
41
|
*/
|
|
45
42
|
async authenticate(player, credentials) {
|
|
46
|
-
|
|
47
|
-
return { success: false, error: "API Auth implementation pending" };
|
|
43
|
+
return this.requestAuth("authenticate", player, credentials);
|
|
48
44
|
}
|
|
49
45
|
/**
|
|
50
46
|
* Registers a player identity via the external API.
|
|
@@ -54,7 +50,7 @@ let ApiAuthProvider = class ApiAuthProvider extends Server.AuthProviderContract
|
|
|
54
50
|
* @returns A promise resolving to the remote registration result.
|
|
55
51
|
*/
|
|
56
52
|
async register(player, credentials) {
|
|
57
|
-
return
|
|
53
|
+
return this.requestAuth("register", player, credentials);
|
|
58
54
|
}
|
|
59
55
|
/**
|
|
60
56
|
* Validates the player's remote session.
|
|
@@ -63,7 +59,7 @@ let ApiAuthProvider = class ApiAuthProvider extends Server.AuthProviderContract
|
|
|
63
59
|
* @returns A promise resolving to the remote session validation result.
|
|
64
60
|
*/
|
|
65
61
|
async validateSession(player) {
|
|
66
|
-
return
|
|
62
|
+
return this.requestAuth("session", player, {});
|
|
67
63
|
}
|
|
68
64
|
/**
|
|
69
65
|
* Notifies the external API that the player has logged out.
|
|
@@ -71,12 +67,90 @@ let ApiAuthProvider = class ApiAuthProvider extends Server.AuthProviderContract
|
|
|
71
67
|
* @param player - The framework player entity.
|
|
72
68
|
*/
|
|
73
69
|
async logout(player) {
|
|
74
|
-
|
|
70
|
+
const result = await this.requestAuth("logout", player, {});
|
|
71
|
+
if (result.success) {
|
|
72
|
+
player.unlinkAccount();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async requestAuth(action, player, credentials) {
|
|
76
|
+
const config = this.options.auth.api;
|
|
77
|
+
if (!config?.baseUrl) {
|
|
78
|
+
return { success: false, error: "API auth is not configured" };
|
|
79
|
+
}
|
|
80
|
+
const identifiers = player.getPlayerIdentifiers();
|
|
81
|
+
const primaryIdentifier = this.options.auth.primaryIdentifier || "license";
|
|
82
|
+
const primary = identifiers.find((identifier) => identifier.type === primaryIdentifier);
|
|
83
|
+
const payload = {
|
|
84
|
+
action,
|
|
85
|
+
accountId: player.accountID ?? null,
|
|
86
|
+
primaryIdentifier: primary?.value ?? null,
|
|
87
|
+
identifiers: identifiers.map((identifier) => ({
|
|
88
|
+
type: identifier.type,
|
|
89
|
+
value: identifier.value,
|
|
90
|
+
})),
|
|
91
|
+
credentials,
|
|
92
|
+
};
|
|
93
|
+
try {
|
|
94
|
+
const response = await fetch(this.resolveUrl(config, action), {
|
|
95
|
+
method: "POST",
|
|
96
|
+
headers: {
|
|
97
|
+
"Content-Type": "application/json",
|
|
98
|
+
...config.headers,
|
|
99
|
+
},
|
|
100
|
+
body: JSON.stringify(payload),
|
|
101
|
+
signal: this.getAbortSignal(config.timeoutMs),
|
|
102
|
+
});
|
|
103
|
+
if (!response.ok) {
|
|
104
|
+
return {
|
|
105
|
+
success: false,
|
|
106
|
+
error: `API auth failed (${response.status})`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const data = (await response.json());
|
|
110
|
+
if (!data.success || !data.accountId) {
|
|
111
|
+
return {
|
|
112
|
+
success: false,
|
|
113
|
+
error: data.error ?? "Authentication rejected",
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
if (action !== "logout") {
|
|
117
|
+
player.linkAccount(String(data.accountId));
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
success: true,
|
|
121
|
+
accountID: String(data.accountId),
|
|
122
|
+
isNewAccount: data.isNewAccount,
|
|
123
|
+
account: data.account,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
return {
|
|
128
|
+
success: false,
|
|
129
|
+
error: error instanceof Error ? error.message : "API auth error",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
resolveUrl(config, action) {
|
|
134
|
+
const base = config.baseUrl.replace(/\/$/, "");
|
|
135
|
+
const pathMap = {
|
|
136
|
+
authenticate: config.authPath ?? "/auth",
|
|
137
|
+
register: config.registerPath ?? "/register",
|
|
138
|
+
session: config.sessionPath ?? "/session",
|
|
139
|
+
logout: config.logoutPath ?? "/logout",
|
|
140
|
+
};
|
|
141
|
+
return `${base}${pathMap[action]}`;
|
|
142
|
+
}
|
|
143
|
+
getAbortSignal(timeoutMs) {
|
|
144
|
+
if (!timeoutMs || timeoutMs <= 0)
|
|
145
|
+
return undefined;
|
|
146
|
+
const controller = new AbortController();
|
|
147
|
+
setTimeout(() => controller.abort(), timeoutMs);
|
|
148
|
+
return controller.signal;
|
|
75
149
|
}
|
|
76
150
|
};
|
|
77
151
|
ApiAuthProvider = __decorate([
|
|
78
152
|
injectable(),
|
|
79
153
|
__param(0, inject(IDENTITY_OPTIONS)),
|
|
80
|
-
__metadata("design:paramtypes", [Object
|
|
154
|
+
__metadata("design:paramtypes", [Object])
|
|
81
155
|
], ApiAuthProvider);
|
|
82
156
|
export { ApiAuthProvider };
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import { Server } from "@open-core/framework";
|
|
1
|
+
import { Server } from "@open-core/framework/server";
|
|
2
2
|
import { IdentityStore, RoleStore } from "../../contracts";
|
|
3
|
+
import { AuthResult, AuthService } from "../../auth.service";
|
|
3
4
|
/**
|
|
4
5
|
* Authentication provider for username and password credentials.
|
|
5
6
|
*
|
|
6
|
-
* This provider
|
|
7
|
-
*
|
|
8
|
-
* of {@link IdentityStore} that supports username-based lookups.
|
|
7
|
+
* This provider uses bcrypt for password hashing and validation. It requires
|
|
8
|
+
* an implementation of {@link IdentityStore} that supports username lookups.
|
|
9
9
|
*
|
|
10
10
|
* @injectable
|
|
11
11
|
* @public
|
|
12
12
|
*/
|
|
13
|
-
export declare class CredentialsAuthProvider extends
|
|
13
|
+
export declare class CredentialsAuthProvider extends AuthService {
|
|
14
14
|
private readonly accountStore;
|
|
15
15
|
private readonly roleStore;
|
|
16
16
|
/** Cost factor for bcrypt hashing */
|
|
17
17
|
private readonly saltRounds;
|
|
18
|
-
constructor(accountStore: IdentityStore
|
|
18
|
+
constructor(accountStore: IdentityStore, roleStore: RoleStore);
|
|
19
19
|
/**
|
|
20
20
|
* Authenticates a player using a username and password.
|
|
21
21
|
*
|
|
@@ -23,7 +23,7 @@ export declare class CredentialsAuthProvider extends Server.AuthProviderContract
|
|
|
23
23
|
* @param credentials - Object containing `username` and `password` strings.
|
|
24
24
|
* @returns A promise resolving to the authentication result.
|
|
25
25
|
*/
|
|
26
|
-
authenticate(player: Server.Player, credentials: Record<string, unknown>): Promise<
|
|
26
|
+
authenticate(player: Server.Player, credentials: Record<string, unknown>): Promise<AuthResult>;
|
|
27
27
|
/**
|
|
28
28
|
* Registers a new account with a username and password.
|
|
29
29
|
*
|
|
@@ -31,14 +31,14 @@ export declare class CredentialsAuthProvider extends Server.AuthProviderContract
|
|
|
31
31
|
* @param credentials - Object containing `username` and `password` strings.
|
|
32
32
|
* @returns A promise resolving to the registration result.
|
|
33
33
|
*/
|
|
34
|
-
register(player: Server.Player, credentials: Record<string, unknown>): Promise<
|
|
34
|
+
register(player: Server.Player, credentials: Record<string, unknown>): Promise<AuthResult>;
|
|
35
35
|
/**
|
|
36
36
|
* Validates if the player's current linked account session is still active.
|
|
37
37
|
*
|
|
38
38
|
* @param player - The framework player entity.
|
|
39
39
|
* @returns A promise resolving to the validation result.
|
|
40
40
|
*/
|
|
41
|
-
validateSession(player: Server.Player): Promise<
|
|
41
|
+
validateSession(player: Server.Player): Promise<AuthResult>;
|
|
42
42
|
/**
|
|
43
43
|
* Performs logout logic for the player.
|
|
44
44
|
*
|