Skip to content

Commit dce8eb1

Browse files
authored
feat(auth): refactor auth configuration logic
2 parents 2d356de + 8232877 commit dce8eb1

File tree

5 files changed

+141
-51
lines changed

5 files changed

+141
-51
lines changed

README.md

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,10 @@ If your Strapi instance uses API tokens, configure the SDK like this:
108108

109109
```typescript
110110
const sdk = strapi({
111+
// Endpoint configuration
111112
baseURL: 'http://localhost:1337/api',
112-
auth: {
113-
strategy: 'api-token',
114-
options: { token: 'your-api-token-here' },
115-
},
113+
// Auth configuration
114+
auth: 'your-api-token-here',
116115
});
117116
```
118117

@@ -239,16 +238,15 @@ The `debug` tool allows you to control logs using wildcard patterns (`*`):
239238

240239
Below is a list of available namespaces to use:
241240

242-
| Namespace | Description |
243-
| ---------------------------------------- | ----------------------------------------------------------------------------------------- |
244-
| `strapi:core` | Logs SDK initialization, configuration validation, and HTTP client setup. |
245-
| `strapi:validators:config` | Logs details related to SDK configuration validation. |
246-
| `strapi:validators:url` | Logs URL validation processes. |
247-
| `strapi:http` | Logs HTTP client setup, request processing, and response/error handling. |
248-
| `strapi:auth:factory` | Logs the registration and creation of authentication providers. |
249-
| `strapi:auth:manager` | Logs authentication lifecycle management. |
250-
| `strapi:auth:provider:api-token` | Logs operations related to API token authentication. |
251-
| `strapi:auth:provider:users-permissions` | Logs operations related to user and permissions-based authentication. |
252-
| `strapi:ct:collection` | Logs interactions with collection-type content managers. |
253-
| `strapi:ct:single` | Logs interactions with single-type content managers. |
254-
| `strapi:utils:url-helper` | Logs URL helper utility operations (e.g., appending query parameters or formatting URLs). |
241+
| Namespace | Description |
242+
| -------------------------------- | ----------------------------------------------------------------------------------------- |
243+
| `strapi:core` | Logs SDK initialization, configuration validation, and HTTP client setup. |
244+
| `strapi:validators:config` | Logs details related to SDK configuration validation. |
245+
| `strapi:validators:url` | Logs URL validation processes. |
246+
| `strapi:http` | Logs HTTP client setup, request processing, and response/error handling. |
247+
| `strapi:auth:factory` | Logs the registration and creation of authentication providers. |
248+
| `strapi:auth:manager` | Logs authentication lifecycle management. |
249+
| `strapi:auth:provider:api-token` | Logs operations related to API token authentication. |
250+
| `strapi:ct:collection` | Logs interactions with collection-type content managers. |
251+
| `strapi:ct:single` | Logs interactions with single-type content managers. |
252+
| `strapi:utils:url-helper` | Logs URL helper utility operations (e.g., appending query parameters or formatting URLs). |

src/index.ts

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,46 @@
1+
import { ApiTokenAuthProvider } from './auth';
12
import { Strapi } from './sdk';
2-
import { StrapiConfigValidator } from './validators';
33

44
import type { StrapiConfig } from './sdk';
55

66
export * from './errors';
77

8+
export interface Config {
9+
/**
10+
* The base URL of the Strapi content API.
11+
*
12+
* This specifies where the SDK should send requests.
13+
*
14+
* The URL must include the protocol (`http` or `https`) and serve
15+
* as the root path for all later API operations.
16+
*
17+
* @example
18+
* 'https://api.example.com'
19+
*
20+
* @remarks
21+
* Failing to provide a valid HTTP or HTTPS URL results in a
22+
* `StrapiInitializationError`.
23+
*/
24+
baseURL: string;
25+
26+
/**
27+
* API token to authenticate requests (optional).
28+
*
29+
* When provided, this token is included in the `Authorization` header
30+
* of every request to the Strapi API.
31+
*
32+
* @remarks
33+
* - A valid token must be a non-empty string.
34+
*
35+
* - If the token is invalid or improperly formatted, the SDK
36+
* throws a `StrapiValidationError` during initialization.
37+
*
38+
* - If excluded, the SDK operates without authentication.
39+
*/
40+
41+
auth?: string;
42+
}
43+
844
/**
945
* Creates a new instance of the Strapi SDK with a specified configuration.
1046
*
@@ -25,10 +61,7 @@ export * from './errors';
2561
* // Basic configuration using API token auth
2662
* const config = {
2763
* baseURL: 'https://api.example.com',
28-
* auth: {
29-
* strategy: 'api-token',
30-
* options: { token: 'your_token_here' }
31-
* }
64+
* auth: 'your_token_here',
3265
* };
3366
*
3467
* // Create the SDK instance
@@ -44,13 +77,20 @@ export * from './errors';
4477
* @throws {StrapiInitializationError} If the provided baseURL doesn't conform to a valid HTTP or HTTPS URL,
4578
* or if the auth configuration is invalid.
4679
*/
47-
export const strapi = (config: StrapiConfig) => {
48-
const configValidator = new StrapiConfigValidator();
49-
50-
return new Strapi<typeof config>(
51-
// Properties
52-
config,
53-
// Dependencies
54-
configValidator
55-
);
80+
export const strapi = (config: Config) => {
81+
const { baseURL, auth } = config;
82+
83+
const sdkConfig: StrapiConfig = { baseURL };
84+
85+
// In this factory, while there is only one auth strategy available, users can't manually set the strategy options.
86+
// Since the SDK constructor needs to define a proper strategy,
87+
// it is handled here if the auth property is provided
88+
if (auth !== undefined) {
89+
sdkConfig.auth = {
90+
strategy: ApiTokenAuthProvider.identifier,
91+
options: { token: auth },
92+
};
93+
}
94+
95+
return new Strapi(sdkConfig);
5696
};

src/sdk.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import createDebug from 'debug';
22

33
import { AuthManager } from './auth';
44
import { CollectionTypeManager, SingleTypeManager } from './content-types';
5-
import { StrapiInitializationError } from './errors';
5+
import { StrapiError, StrapiInitializationError } from './errors';
66
import { HttpClient } from './http';
77
import { AuthInterceptors, HttpInterceptors } from './interceptors';
88
import { StrapiConfigValidator } from './validators';
@@ -39,12 +39,10 @@ export interface AuthConfig<T = unknown> {
3939
*
4040
* It serves as the main interface through which users interact with
4141
* their Strapi installation programmatically.
42-
*
43-
* @template T_Config - Configuration type inferred from the user-provided SDK configuration
4442
*/
45-
export class Strapi<const T_Config extends StrapiConfig = StrapiConfig> {
43+
export class Strapi {
4644
/** @internal */
47-
private readonly _config: T_Config;
45+
private readonly _config: StrapiConfig;
4846

4947
/** @internal */
5048
private readonly _validator: StrapiConfigValidator;
@@ -58,7 +56,7 @@ export class Strapi<const T_Config extends StrapiConfig = StrapiConfig> {
5856
/** @internal */
5957
constructor(
6058
// Properties
61-
config: T_Config,
59+
config: StrapiConfig,
6260

6361
// Dependencies
6462
validator: StrapiConfigValidator = new StrapiConfigValidator(),
@@ -189,7 +187,14 @@ export class Strapi<const T_Config extends StrapiConfig = StrapiConfig> {
189187

190188
debug('setting up the auth strategy using %o', strategy);
191189

192-
this._authManager.setStrategy(strategy, options);
190+
try {
191+
this._authManager.setStrategy(strategy, options);
192+
} catch (e) {
193+
throw new StrapiInitializationError(
194+
e,
195+
`Failed to initialize the SDK auth manager: ${e instanceof StrapiError ? e.cause : e}`
196+
);
197+
}
193198
}
194199

195200
this._httpClient.interceptors.request

tests/unit/index.test.ts

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import { strapi, StrapiInitializationError, StrapiValidationError } from '../../src';
1+
import { strapi, StrapiInitializationError } from '../../src';
2+
import { ApiTokenAuthProvider } from '../../src/auth';
23
import { Strapi } from '../../src/sdk';
34

4-
import type { StrapiConfig } from '../../src/sdk';
5+
import type { Config } from '../../src';
56

67
describe('strapi', () => {
7-
it('should create an SDK instance with valid configuration', () => {
8+
it('should create an SDK instance with valid http configuration', () => {
89
// Arrange
9-
const config = { baseURL: 'https://api.example.com' } satisfies StrapiConfig;
10+
const config = { baseURL: 'https://api.example.com' } satisfies Config;
1011

1112
// Act
1213
const sdk = strapi(config);
@@ -16,9 +17,25 @@ describe('strapi', () => {
1617
expect(sdk).toHaveProperty('baseURL', config.baseURL);
1718
});
1819

20+
it('should create an SDK instance with valid auth configuration', () => {
21+
// Arrange
22+
const token = '<token>';
23+
const config = { baseURL: 'https://api.example.com', auth: token } satisfies Config;
24+
25+
// Act
26+
const sdk = strapi(config);
27+
28+
// Assert
29+
expect(sdk).toBeInstanceOf(Strapi);
30+
expect(sdk).toHaveProperty('auth', {
31+
strategy: ApiTokenAuthProvider.identifier, // default auth strategy
32+
options: { token },
33+
});
34+
});
35+
1936
it('should throw an error for an invalid baseURL', () => {
2037
// Arrange
21-
const config = { baseURL: 'invalid-url' } satisfies StrapiConfig;
38+
const config = { baseURL: 'invalid-url' } satisfies Config;
2239

2340
// Act & Assert
2441
expect(() => strapi(config)).toThrow(StrapiInitializationError);
@@ -28,13 +45,10 @@ describe('strapi', () => {
2845
// Arrange
2946
const config = {
3047
baseURL: 'https://api.example.com',
31-
auth: {
32-
strategy: 'api-token',
33-
options: { token: '' }, // Invalid token
34-
},
35-
} satisfies StrapiConfig;
48+
auth: '', // Invalid API token
49+
} satisfies Config;
3650

3751
// Act & Assert
38-
expect(() => strapi(config)).toThrow(StrapiValidationError);
52+
expect(() => strapi(config)).toThrow(StrapiInitializationError);
3953
});
4054
});

tests/unit/sdk.test.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import {
66
HTTPInternalServerError,
77
HTTPNotFoundError,
88
HTTPTimeoutError,
9+
StrapiError,
910
StrapiInitializationError,
11+
StrapiValidationError,
1012
} from '../../src';
1113
import { CollectionTypeManager, SingleTypeManager } from '../../src/content-types';
1214
import { HttpClient, StatusCode } from '../../src/http';
@@ -46,7 +48,7 @@ describe('Strapi', () => {
4648
// Arrange
4749
const config = {
4850
baseURL: 'https://localhost:1337/api',
49-
auth: { strategy: MockAuthProvider.identifier, options: {} },
51+
auth: { strategy: MockAuthProvider.identifier },
5052
} satisfies StrapiConfig;
5153

5254
const mockValidator = new MockStrapiConfigValidator();
@@ -62,7 +64,7 @@ describe('Strapi', () => {
6264

6365
expect(sdk).toBeInstanceOf(Strapi);
6466
expect(validatorSpy).toHaveBeenCalledWith(config);
65-
expect(authSetStrategySpy).toHaveBeenCalledWith(MockAuthProvider.identifier, {});
67+
expect(authSetStrategySpy).toHaveBeenCalledWith(MockAuthProvider.identifier, undefined);
6668
});
6769

6870
it('should not set the auth strategy if no auth config is provided', () => {
@@ -82,6 +84,37 @@ describe('Strapi', () => {
8284
expect(authSetStrategySpy).not.toHaveBeenCalled();
8385
});
8486

87+
it.each([
88+
['common error', new Error('unexpected error')],
89+
['strapi error', new StrapiError(new StrapiValidationError('invalid auth configuration'))],
90+
])(
91+
'should throw an initialization error if a %s error occurs during the auth strategy init',
92+
async (_title, error) => {
93+
// Arrange
94+
const config = {
95+
baseURL: 'https://localhost:1337/api',
96+
auth: { strategy: MockAuthProvider.identifier },
97+
} satisfies StrapiConfig;
98+
99+
const mockValidator = new MockStrapiConfigValidator();
100+
const mockAuthManager = new MockAuthManager();
101+
102+
jest.spyOn(mockAuthManager, 'setStrategy').mockImplementationOnce(() => {
103+
throw error;
104+
});
105+
106+
// Act
107+
expect(
108+
() => new Strapi(config, mockValidator, mockAuthManager, mockHttpClientFactory)
109+
).toThrow(StrapiInitializationError);
110+
111+
expect(mockAuthManager.setStrategy).toHaveBeenCalledWith(
112+
MockAuthProvider.identifier,
113+
undefined
114+
);
115+
}
116+
);
117+
85118
it('should throw an error on invalid baseURL', () => {
86119
// Arrange
87120
const config = { baseURL: 'invalid-url' } satisfies StrapiConfig;

0 commit comments

Comments
 (0)