@lenne.tech/nest-server 11.12.0 → 11.13.1
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/dist/config.env.js +1 -0
- package/dist/config.env.js.map +1 -1
- package/dist/core/common/interfaces/server-options.interface.d.ts +16 -0
- package/dist/core/modules/auth/guards/roles.guard.js +1 -1
- package/dist/core/modules/auth/guards/roles.guard.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.config.d.ts +13 -0
- package/dist/core/modules/better-auth/better-auth.config.js +68 -18
- package/dist/core/modules/better-auth/better-auth.config.js.map +1 -1
- package/dist/core/modules/better-auth/better-auth.resolver.d.ts +7 -3
- package/dist/core/modules/better-auth/better-auth.resolver.js +16 -6
- package/dist/core/modules/better-auth/better-auth.resolver.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-api.middleware.d.ts +2 -2
- package/dist/core/modules/better-auth/core-better-auth-api.middleware.js +46 -2
- package/dist/core/modules/better-auth/core-better-auth-api.middleware.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-auth.model.d.ts +1 -0
- package/dist/core/modules/better-auth/core-better-auth-auth.model.js +7 -0
- package/dist/core/modules/better-auth/core-better-auth-auth.model.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-email-verification.service.d.ts +48 -0
- package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js +241 -0
- package/dist/core/modules/better-auth/core-better-auth-email-verification.service.js.map +1 -0
- package/dist/core/modules/better-auth/core-better-auth-models.d.ts +2 -1
- package/dist/core/modules/better-auth/core-better-auth-models.js +8 -4
- package/dist/core/modules/better-auth/core-better-auth-models.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-signup-validator.service.d.ts +18 -0
- package/dist/core/modules/better-auth/core-better-auth-signup-validator.service.js +82 -0
- package/dist/core/modules/better-auth/core-better-auth-signup-validator.service.js.map +1 -0
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.d.ts +0 -1
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.js +15 -8
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth-web.helper.d.ts +4 -1
- package/dist/core/modules/better-auth/core-better-auth-web.helper.js +41 -30
- package/dist/core/modules/better-auth/core-better-auth-web.helper.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.controller.d.ts +11 -1
- package/dist/core/modules/better-auth/core-better-auth.controller.js +91 -15
- package/dist/core/modules/better-auth/core-better-auth.controller.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.middleware.js +93 -45
- package/dist/core/modules/better-auth/core-better-auth.middleware.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.module.d.ts +6 -0
- package/dist/core/modules/better-auth/core-better-auth.module.js +131 -25
- package/dist/core/modules/better-auth/core-better-auth.module.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.resolver.d.ts +12 -5
- package/dist/core/modules/better-auth/core-better-auth.resolver.js +64 -17
- package/dist/core/modules/better-auth/core-better-auth.resolver.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.service.d.ts +4 -1
- package/dist/core/modules/better-auth/core-better-auth.service.js +123 -23
- package/dist/core/modules/better-auth/core-better-auth.service.js.map +1 -1
- package/dist/core/modules/better-auth/index.d.ts +2 -0
- package/dist/core/modules/better-auth/index.js +2 -0
- package/dist/core/modules/better-auth/index.js.map +1 -1
- package/dist/core/modules/error-code/error-codes.d.ts +45 -0
- package/dist/core/modules/error-code/error-codes.js +40 -0
- package/dist/core/modules/error-code/error-codes.js.map +1 -1
- package/dist/core/modules/user/core-user.model.d.ts +1 -0
- package/dist/core/modules/user/core-user.model.js +11 -0
- package/dist/core/modules/user/core-user.model.js.map +1 -1
- package/dist/server/modules/better-auth/better-auth.controller.d.ts +3 -1
- package/dist/server/modules/better-auth/better-auth.controller.js +12 -3
- package/dist/server/modules/better-auth/better-auth.controller.js.map +1 -1
- package/dist/server/modules/better-auth/better-auth.resolver.d.ts +7 -3
- package/dist/server/modules/better-auth/better-auth.resolver.js +16 -6
- package/dist/server/modules/better-auth/better-auth.resolver.js.map +1 -1
- package/dist/server/modules/error-code/error-codes.d.ts +5 -0
- package/dist/server/modules/user/user.model.d.ts +5 -0
- package/dist/templates/email-verification-de.ejs +78 -0
- package/dist/templates/email-verification-en.ejs +78 -0
- package/dist/test/test.helper.d.ts +4 -0
- package/dist/test/test.helper.js +54 -1
- package/dist/test/test.helper.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +3 -11
- package/src/config.env.ts +2 -0
- package/src/core/common/interfaces/server-options.interface.ts +240 -0
- package/src/core/modules/auth/guards/roles.guard.ts +4 -1
- package/src/core/modules/better-auth/INTEGRATION-CHECKLIST.md +113 -0
- package/src/core/modules/better-auth/README.md +72 -7
- package/src/core/modules/better-auth/better-auth.config.ts +168 -42
- package/src/core/modules/better-auth/better-auth.resolver.ts +16 -5
- package/src/core/modules/better-auth/core-better-auth-api.middleware.ts +65 -8
- package/src/core/modules/better-auth/core-better-auth-auth.model.ts +10 -0
- package/src/core/modules/better-auth/core-better-auth-email-verification.service.ts +433 -0
- package/src/core/modules/better-auth/core-better-auth-models.ts +6 -3
- package/src/core/modules/better-auth/core-better-auth-signup-validator.service.ts +178 -0
- package/src/core/modules/better-auth/core-better-auth-user.mapper.ts +18 -14
- package/src/core/modules/better-auth/core-better-auth-web.helper.ts +67 -46
- package/src/core/modules/better-auth/core-better-auth.controller.ts +155 -16
- package/src/core/modules/better-auth/core-better-auth.middleware.ts +148 -70
- package/src/core/modules/better-auth/core-better-auth.module.ts +226 -40
- package/src/core/modules/better-auth/core-better-auth.resolver.ts +140 -20
- package/src/core/modules/better-auth/core-better-auth.service.ts +181 -25
- package/src/core/modules/better-auth/index.ts +2 -0
- package/src/core/modules/error-code/error-codes.ts +45 -0
- package/src/core/modules/user/core-user.model.ts +15 -0
- package/src/server/modules/better-auth/better-auth.controller.ts +6 -2
- package/src/server/modules/better-auth/better-auth.resolver.ts +16 -5
- package/src/templates/email-verification-de.ejs +78 -0
- package/src/templates/email-verification-en.ejs +78 -0
- package/src/test/README.md +190 -0
- package/src/test/test.helper.ts +82 -1
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { Optional } from '@nestjs/common';
|
|
1
2
|
import { Args, Context, Mutation, Query, Resolver } from '@nestjs/graphql';
|
|
2
3
|
import { Request, Response } from 'express';
|
|
3
4
|
|
|
4
5
|
import { Roles } from '../../../core/common/decorators/roles.decorator';
|
|
5
6
|
import { RoleEnum } from '../../../core/common/enums/role.enum';
|
|
6
7
|
import { CoreBetterAuthAuthModel } from '../../../core/modules/better-auth/core-better-auth-auth.model';
|
|
8
|
+
import { CoreBetterAuthEmailVerificationService } from '../../../core/modules/better-auth/core-better-auth-email-verification.service';
|
|
7
9
|
import { CoreBetterAuthMigrationStatusModel } from '../../../core/modules/better-auth/core-better-auth-migration-status.model';
|
|
8
10
|
import {
|
|
9
11
|
CoreBetterAuth2FASetupModel,
|
|
@@ -12,6 +14,7 @@ import {
|
|
|
12
14
|
CoreBetterAuthPasskeyModel,
|
|
13
15
|
CoreBetterAuthSessionModel,
|
|
14
16
|
} from '../../../core/modules/better-auth/core-better-auth-models';
|
|
17
|
+
import { CoreBetterAuthSignUpValidatorService } from '../../../core/modules/better-auth/core-better-auth-signup-validator.service';
|
|
15
18
|
import { CoreBetterAuthUserMapper } from '../../../core/modules/better-auth/core-better-auth-user.mapper';
|
|
16
19
|
import { CoreBetterAuthResolver } from '../../../core/modules/better-auth/core-better-auth.resolver';
|
|
17
20
|
import { CoreBetterAuthService } from '../../../core/modules/better-auth/core-better-auth.service';
|
|
@@ -29,8 +32,13 @@ import { CoreBetterAuthService } from '../../../core/modules/better-auth/core-be
|
|
|
29
32
|
* @example
|
|
30
33
|
* ```typescript
|
|
31
34
|
* // Add custom behavior after sign-up
|
|
32
|
-
* override async betterAuthSignUp(
|
|
33
|
-
*
|
|
35
|
+
* override async betterAuthSignUp(
|
|
36
|
+
* email: string,
|
|
37
|
+
* password: string,
|
|
38
|
+
* name?: string,
|
|
39
|
+
* termsAndPrivacyAccepted?: boolean,
|
|
40
|
+
* ) {
|
|
41
|
+
* const result = await super.betterAuthSignUp(email, password, name, termsAndPrivacyAccepted);
|
|
34
42
|
*
|
|
35
43
|
* if (result.success && result.user) {
|
|
36
44
|
* await this.emailService.sendWelcomeEmail(result.user.email);
|
|
@@ -46,8 +54,10 @@ export class BetterAuthResolver extends CoreBetterAuthResolver {
|
|
|
46
54
|
constructor(
|
|
47
55
|
protected override readonly betterAuthService: CoreBetterAuthService,
|
|
48
56
|
protected override readonly userMapper: CoreBetterAuthUserMapper,
|
|
57
|
+
@Optional() protected override readonly signUpValidator?: CoreBetterAuthSignUpValidatorService,
|
|
58
|
+
@Optional() protected override readonly emailVerificationService?: CoreBetterAuthEmailVerificationService,
|
|
49
59
|
) {
|
|
50
|
-
super(betterAuthService, userMapper);
|
|
60
|
+
super(betterAuthService, userMapper, signUpValidator, emailVerificationService);
|
|
51
61
|
}
|
|
52
62
|
|
|
53
63
|
// ===========================================================================
|
|
@@ -112,7 +122,7 @@ export class BetterAuthResolver extends CoreBetterAuthResolver {
|
|
|
112
122
|
override async betterAuthSignIn(
|
|
113
123
|
@Args('email') email: string,
|
|
114
124
|
@Args('password') password: string,
|
|
115
|
-
@Context() ctx
|
|
125
|
+
@Context() ctx?: { req: Request; res: Response },
|
|
116
126
|
): Promise<CoreBetterAuthAuthModel> {
|
|
117
127
|
return super.betterAuthSignIn(email, password, ctx);
|
|
118
128
|
}
|
|
@@ -125,8 +135,9 @@ export class BetterAuthResolver extends CoreBetterAuthResolver {
|
|
|
125
135
|
@Args('email') email: string,
|
|
126
136
|
@Args('password') password: string,
|
|
127
137
|
@Args('name', { nullable: true }) name?: string,
|
|
138
|
+
@Args('termsAndPrivacyAccepted', { nullable: true }) termsAndPrivacyAccepted?: boolean,
|
|
128
139
|
): Promise<CoreBetterAuthAuthModel> {
|
|
129
|
-
return super.betterAuthSignUp(email, password, name);
|
|
140
|
+
return super.betterAuthSignUp(email, password, name, termsAndPrivacyAccepted);
|
|
130
141
|
}
|
|
131
142
|
|
|
132
143
|
@Mutation(() => Boolean, { description: 'Sign out via Better-Auth' })
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="de">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>E-Mail-Adresse bestätigen</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
10
|
+
line-height: 1.6;
|
|
11
|
+
color: #333;
|
|
12
|
+
max-width: 600px;
|
|
13
|
+
margin: 0 auto;
|
|
14
|
+
padding: 20px;
|
|
15
|
+
}
|
|
16
|
+
.container {
|
|
17
|
+
background-color: #ffffff;
|
|
18
|
+
border-radius: 8px;
|
|
19
|
+
padding: 30px;
|
|
20
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
21
|
+
}
|
|
22
|
+
h1 {
|
|
23
|
+
color: #2563eb;
|
|
24
|
+
margin-bottom: 20px;
|
|
25
|
+
}
|
|
26
|
+
.button {
|
|
27
|
+
display: inline-block;
|
|
28
|
+
background-color: #2563eb;
|
|
29
|
+
color: #ffffff !important;
|
|
30
|
+
text-decoration: none;
|
|
31
|
+
padding: 12px 30px;
|
|
32
|
+
border-radius: 6px;
|
|
33
|
+
font-weight: 600;
|
|
34
|
+
margin: 20px 0;
|
|
35
|
+
}
|
|
36
|
+
.button:hover {
|
|
37
|
+
background-color: #1d4ed8;
|
|
38
|
+
}
|
|
39
|
+
.footer {
|
|
40
|
+
margin-top: 30px;
|
|
41
|
+
padding-top: 20px;
|
|
42
|
+
border-top: 1px solid #e5e7eb;
|
|
43
|
+
font-size: 14px;
|
|
44
|
+
color: #6b7280;
|
|
45
|
+
}
|
|
46
|
+
.link-fallback {
|
|
47
|
+
font-size: 12px;
|
|
48
|
+
color: #6b7280;
|
|
49
|
+
word-break: break-all;
|
|
50
|
+
margin-top: 15px;
|
|
51
|
+
}
|
|
52
|
+
</style>
|
|
53
|
+
</head>
|
|
54
|
+
<body>
|
|
55
|
+
<div class="container">
|
|
56
|
+
<h1>Hallo <%= name %>,</h1>
|
|
57
|
+
|
|
58
|
+
<p>Vielen Dank für Ihre Registrierung bei <%= appName %>!</p>
|
|
59
|
+
|
|
60
|
+
<p>Bitte klicken Sie auf die Schaltfläche unten, um Ihre E-Mail-Adresse zu bestätigen:</p>
|
|
61
|
+
|
|
62
|
+
<p style="text-align: center;">
|
|
63
|
+
<a href="<%= link %>" class="button">E-Mail-Adresse bestätigen</a>
|
|
64
|
+
</p>
|
|
65
|
+
|
|
66
|
+
<p>Dieser Link ist <strong><%= expiresIn %></strong> gültig.</p>
|
|
67
|
+
|
|
68
|
+
<div class="footer">
|
|
69
|
+
<p>Wenn Sie kein Konto erstellt haben, können Sie diese E-Mail ignorieren.</p>
|
|
70
|
+
|
|
71
|
+
<p class="link-fallback">
|
|
72
|
+
Falls die Schaltfläche nicht funktioniert, kopieren Sie diesen Link in Ihren Browser:<br>
|
|
73
|
+
<a href="<%= link %>"><%= link %></a>
|
|
74
|
+
</p>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</body>
|
|
78
|
+
</html>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Verify your email address</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
10
|
+
line-height: 1.6;
|
|
11
|
+
color: #333;
|
|
12
|
+
max-width: 600px;
|
|
13
|
+
margin: 0 auto;
|
|
14
|
+
padding: 20px;
|
|
15
|
+
}
|
|
16
|
+
.container {
|
|
17
|
+
background-color: #ffffff;
|
|
18
|
+
border-radius: 8px;
|
|
19
|
+
padding: 30px;
|
|
20
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
21
|
+
}
|
|
22
|
+
h1 {
|
|
23
|
+
color: #2563eb;
|
|
24
|
+
margin-bottom: 20px;
|
|
25
|
+
}
|
|
26
|
+
.button {
|
|
27
|
+
display: inline-block;
|
|
28
|
+
background-color: #2563eb;
|
|
29
|
+
color: #ffffff !important;
|
|
30
|
+
text-decoration: none;
|
|
31
|
+
padding: 12px 30px;
|
|
32
|
+
border-radius: 6px;
|
|
33
|
+
font-weight: 600;
|
|
34
|
+
margin: 20px 0;
|
|
35
|
+
}
|
|
36
|
+
.button:hover {
|
|
37
|
+
background-color: #1d4ed8;
|
|
38
|
+
}
|
|
39
|
+
.footer {
|
|
40
|
+
margin-top: 30px;
|
|
41
|
+
padding-top: 20px;
|
|
42
|
+
border-top: 1px solid #e5e7eb;
|
|
43
|
+
font-size: 14px;
|
|
44
|
+
color: #6b7280;
|
|
45
|
+
}
|
|
46
|
+
.link-fallback {
|
|
47
|
+
font-size: 12px;
|
|
48
|
+
color: #6b7280;
|
|
49
|
+
word-break: break-all;
|
|
50
|
+
margin-top: 15px;
|
|
51
|
+
}
|
|
52
|
+
</style>
|
|
53
|
+
</head>
|
|
54
|
+
<body>
|
|
55
|
+
<div class="container">
|
|
56
|
+
<h1>Hello <%= name %>,</h1>
|
|
57
|
+
|
|
58
|
+
<p>Thank you for registering with <%= appName %>!</p>
|
|
59
|
+
|
|
60
|
+
<p>Please click the button below to verify your email address:</p>
|
|
61
|
+
|
|
62
|
+
<p style="text-align: center;">
|
|
63
|
+
<a href="<%= link %>" class="button">Verify Email Address</a>
|
|
64
|
+
</p>
|
|
65
|
+
|
|
66
|
+
<p>This link will expire in <strong><%= expiresIn %></strong>.</p>
|
|
67
|
+
|
|
68
|
+
<div class="footer">
|
|
69
|
+
<p>If you did not create an account, you can safely ignore this email.</p>
|
|
70
|
+
|
|
71
|
+
<p class="link-fallback">
|
|
72
|
+
If the button doesn't work, copy and paste this link into your browser:<br>
|
|
73
|
+
<a href="<%= link %>"><%= link %></a>
|
|
74
|
+
</p>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</body>
|
|
78
|
+
</html>
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# TestHelper Reference
|
|
2
|
+
|
|
3
|
+
The `TestHelper` class provides utilities for testing GraphQL and REST APIs in `@lenne.tech/nest-server` projects.
|
|
4
|
+
|
|
5
|
+
## Initialization
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { TestHelper } from '@lenne.tech/nest-server';
|
|
9
|
+
|
|
10
|
+
const app = moduleFixture.createNestApplication();
|
|
11
|
+
await app.init();
|
|
12
|
+
const testHelper = new TestHelper(app);
|
|
13
|
+
|
|
14
|
+
// With WebSocket support for subscriptions
|
|
15
|
+
const testHelper = new TestHelper(app, 'ws://127.0.0.1:3030/graphql');
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## REST API Testing (`testHelper.rest()`)
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
const result = await testHelper.rest('/endpoint', options);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### TestRestOptions
|
|
25
|
+
|
|
26
|
+
| Option | Type | Default | Description |
|
|
27
|
+
|--------|------|---------|-------------|
|
|
28
|
+
| `method` | `'GET' \| 'POST' \| 'PUT' \| 'PATCH' \| 'DELETE'` | `'GET'` | HTTP method |
|
|
29
|
+
| `token` | `string` | `null` | Bearer token via Authorization header |
|
|
30
|
+
| `cookies` | `string \| Record<string, string>` | - | Cookie-based authentication (see below) |
|
|
31
|
+
| `headers` | `Record<string, string>` | - | Custom request headers |
|
|
32
|
+
| `payload` | `any` | `null` | Request body |
|
|
33
|
+
| `statusCode` | `number` | `200` | Expected HTTP status code |
|
|
34
|
+
| `returnResponse` | `boolean` | `false` | Return full response including headers |
|
|
35
|
+
| `attachments` | `Record<string, string>` | - | File uploads (key: field name, value: file path) |
|
|
36
|
+
| `log` | `boolean` | `false` | Log request config to console |
|
|
37
|
+
| `logError` | `boolean` | `false` | Log error details when status >= 400 |
|
|
38
|
+
|
|
39
|
+
## Cookie Authentication
|
|
40
|
+
|
|
41
|
+
The `cookies` option supports three modes with **auto-detection**:
|
|
42
|
+
|
|
43
|
+
### 1. Plain Session Token (Auto-Detection)
|
|
44
|
+
|
|
45
|
+
When a string **without** `=` or `;` is provided, it is automatically recognized as a session token and converted via `buildBetterAuthCookies()`:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// Auto-detection: plain token -> sets iam.session_token + token cookies
|
|
49
|
+
const result = await testHelper.rest('/endpoint', {
|
|
50
|
+
cookies: sessionToken,
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This is equivalent to:
|
|
55
|
+
```typescript
|
|
56
|
+
cookies: { 'iam.session_token': sessionToken, 'token': sessionToken }
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 2. Explicit Cookie Pairs
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
const result = await testHelper.rest('/endpoint', {
|
|
63
|
+
cookies: { 'iam.session_token': token, 'custom-cookie': 'value' },
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 3. Raw Cookie String
|
|
68
|
+
|
|
69
|
+
When a string **with** `=` or `;` is provided, it is used as-is:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
const result = await testHelper.rest('/endpoint', {
|
|
73
|
+
cookies: 'iam.session_token=abc; token=xyz',
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### `token` vs `cookies`
|
|
78
|
+
|
|
79
|
+
| Option | Transport | Use Case |
|
|
80
|
+
|--------|-----------|----------|
|
|
81
|
+
| `token` | `Authorization: Bearer <token>` header | JWT authentication |
|
|
82
|
+
| `cookies` | `Cookie` header | Session-based authentication (BetterAuth) |
|
|
83
|
+
|
|
84
|
+
Both can be used simultaneously without conflict - `token` sets the Authorization header while `cookies` sets the Cookie header.
|
|
85
|
+
|
|
86
|
+
## Static Helper Methods
|
|
87
|
+
|
|
88
|
+
### `TestHelper.buildBetterAuthCookies(sessionToken, basePath?)`
|
|
89
|
+
|
|
90
|
+
Build a cookie Record for BetterAuth session authentication:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
const cookies = TestHelper.buildBetterAuthCookies('session-token-value');
|
|
94
|
+
// Result: { 'iam.session_token': 'session-token-value', 'token': 'session-token-value' }
|
|
95
|
+
|
|
96
|
+
// Custom base path
|
|
97
|
+
const cookies = TestHelper.buildBetterAuthCookies('token', 'auth');
|
|
98
|
+
// Result: { 'auth.session_token': 'token', 'token': 'token' }
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### `TestHelper.extractSessionToken(response, cookieName?)`
|
|
102
|
+
|
|
103
|
+
Extract a session token from Set-Cookie headers. Handles signed cookies (`value.signature` format):
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
const response = await testHelper.rest('/iam/sign-in/email', {
|
|
107
|
+
method: 'POST',
|
|
108
|
+
payload: { email, password },
|
|
109
|
+
returnResponse: true,
|
|
110
|
+
});
|
|
111
|
+
const sessionToken = TestHelper.extractSessionToken(response);
|
|
112
|
+
// Returns the token value from 'iam.session_token' cookie
|
|
113
|
+
|
|
114
|
+
// Custom cookie name
|
|
115
|
+
const token = TestHelper.extractSessionToken(response, 'custom.session_token');
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `TestHelper.extractCookies(response)`
|
|
119
|
+
|
|
120
|
+
Extract all Set-Cookie values as a `Record<name, value>`:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
const response = await testHelper.rest('/iam/sign-in/email', {
|
|
124
|
+
method: 'POST',
|
|
125
|
+
payload: { email, password },
|
|
126
|
+
returnResponse: true,
|
|
127
|
+
});
|
|
128
|
+
const cookies = TestHelper.extractCookies(response);
|
|
129
|
+
// Result: { 'iam.session_token': 'abc.sig', 'token': 'abc.sig', ... }
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Practical Examples
|
|
133
|
+
|
|
134
|
+
### JWT Authentication
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
const signIn = await testHelper.rest('/iam/sign-in/email', {
|
|
138
|
+
method: 'POST',
|
|
139
|
+
payload: { email: 'user@test.com', password: 'Password123!' },
|
|
140
|
+
});
|
|
141
|
+
const jwtToken = signIn.token;
|
|
142
|
+
|
|
143
|
+
await testHelper.rest('/protected-endpoint', {
|
|
144
|
+
token: jwtToken,
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Cookie Authentication (Auto-Detection)
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// Get session token from database after sign-in
|
|
152
|
+
const session = await db.collection('session').findOne({ userId: user._id });
|
|
153
|
+
|
|
154
|
+
// Use session token with auto-detection
|
|
155
|
+
await testHelper.rest('/protected-endpoint', {
|
|
156
|
+
cookies: session.token, // Auto -> iam.session_token=...; token=...
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Extract Session Token from Response
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
const response = await testHelper.rest('/iam/sign-in/email', {
|
|
164
|
+
method: 'POST',
|
|
165
|
+
payload: { email, password },
|
|
166
|
+
returnResponse: true,
|
|
167
|
+
});
|
|
168
|
+
const sessionToken = TestHelper.extractSessionToken(response);
|
|
169
|
+
|
|
170
|
+
// Use extracted token for subsequent requests
|
|
171
|
+
await testHelper.rest('/protected-endpoint', {
|
|
172
|
+
cookies: sessionToken,
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## GraphQL Testing (`testHelper.graphQl()`)
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const result = await testHelper.graphQl({
|
|
180
|
+
name: 'findUsers',
|
|
181
|
+
type: TestGraphQLType.QUERY,
|
|
182
|
+
arguments: { filter: { email: { eq: 'test@test.com' } } },
|
|
183
|
+
fields: ['id', 'email', 'name'],
|
|
184
|
+
}, {
|
|
185
|
+
token: jwtToken,
|
|
186
|
+
statusCode: 200,
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
See `TestGraphQLConfig` and `TestGraphQLOptions` interfaces in `test.helper.ts` for full configuration options.
|
package/src/test/test.helper.ts
CHANGED
|
@@ -129,6 +129,17 @@ export interface TestGraphQLVariable {
|
|
|
129
129
|
*/
|
|
130
130
|
export interface TestRestOptions {
|
|
131
131
|
attachments?: Record<string, string>;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Cookies for the request. Three usage modes:
|
|
135
|
+
* - string (plain token without = or ;): Auto-detected as session token
|
|
136
|
+
* and converted via buildBetterAuthCookies() to all relevant cookie names
|
|
137
|
+
* (iam.session_token, token)
|
|
138
|
+
* - string (with = or ;): Used as-is as a cookie string
|
|
139
|
+
* - Record<string, string>: Key-value pairs joined into a cookie string
|
|
140
|
+
*/
|
|
141
|
+
cookies?: Record<string, string> | string;
|
|
142
|
+
|
|
132
143
|
headers?: Record<string, string>;
|
|
133
144
|
log?: boolean;
|
|
134
145
|
logError?: boolean;
|
|
@@ -404,10 +415,11 @@ export class TestHelper {
|
|
|
404
415
|
};
|
|
405
416
|
|
|
406
417
|
// Init vars
|
|
407
|
-
const { attachments, log, logError, returnResponse, statusCode, token } = config;
|
|
418
|
+
const { attachments, cookies, log, logError, returnResponse, statusCode, token } = config;
|
|
408
419
|
|
|
409
420
|
// Request configuration
|
|
410
421
|
const requestConfig: any = {
|
|
422
|
+
cookies,
|
|
411
423
|
headers: config.headers,
|
|
412
424
|
method: config.method,
|
|
413
425
|
url,
|
|
@@ -560,6 +572,24 @@ export class TestHelper {
|
|
|
560
572
|
request.set('Authorization', `bearer ${token}`);
|
|
561
573
|
}
|
|
562
574
|
|
|
575
|
+
// Cookies
|
|
576
|
+
if (requestConfig.cookies) {
|
|
577
|
+
let cookieString: string;
|
|
578
|
+
if (typeof requestConfig.cookies === 'string') {
|
|
579
|
+
// Auto-detect: plain token (no = or ;) vs formatted cookie string
|
|
580
|
+
if (!requestConfig.cookies.includes('=') && !requestConfig.cookies.includes(';')) {
|
|
581
|
+
// Plain session token -> auto-build BetterAuth cookies
|
|
582
|
+
const cookieRecord = TestHelper.buildBetterAuthCookies(requestConfig.cookies);
|
|
583
|
+
cookieString = Object.entries(cookieRecord).map(([k, v]) => `${k}=${v}`).join('; ');
|
|
584
|
+
} else {
|
|
585
|
+
cookieString = requestConfig.cookies;
|
|
586
|
+
}
|
|
587
|
+
} else {
|
|
588
|
+
cookieString = Object.entries(requestConfig.cookies).map(([k, v]) => `${k}=${v}`).join('; ');
|
|
589
|
+
}
|
|
590
|
+
request.set('Cookie', cookieString);
|
|
591
|
+
}
|
|
592
|
+
|
|
563
593
|
// Headers
|
|
564
594
|
if (requestConfig.headers) {
|
|
565
595
|
for (const [key, value] of Object.entries(requestConfig.headers)) {
|
|
@@ -724,6 +754,57 @@ export class TestHelper {
|
|
|
724
754
|
return messages;
|
|
725
755
|
}
|
|
726
756
|
|
|
757
|
+
/**
|
|
758
|
+
* Build a cookie Record for BetterAuth session authentication.
|
|
759
|
+
* Sets the token in all relevant cookie names for compatibility.
|
|
760
|
+
*/
|
|
761
|
+
static buildBetterAuthCookies(sessionToken: string, basePath: string = 'iam'): Record<string, string> {
|
|
762
|
+
return {
|
|
763
|
+
[`${basePath}.session_token`]: sessionToken,
|
|
764
|
+
'token': sessionToken,
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Extract all Set-Cookie values from a supertest response as a Record<name, value>.
|
|
770
|
+
*/
|
|
771
|
+
static extractCookies(response: any): Record<string, string> {
|
|
772
|
+
const cookies: Record<string, string> = {};
|
|
773
|
+
const setCookieHeaders = response?.headers?.['set-cookie'];
|
|
774
|
+
if (!setCookieHeaders) {
|
|
775
|
+
return cookies;
|
|
776
|
+
}
|
|
777
|
+
const headerArray = Array.isArray(setCookieHeaders) ? setCookieHeaders : [setCookieHeaders];
|
|
778
|
+
for (const cookie of headerArray) {
|
|
779
|
+
const parts = cookie.split(';')[0];
|
|
780
|
+
const eqIndex = parts.indexOf('=');
|
|
781
|
+
if (eqIndex > 0) {
|
|
782
|
+
const name = parts.substring(0, eqIndex).trim();
|
|
783
|
+
const value = parts.substring(eqIndex + 1).trim();
|
|
784
|
+
cookies[name] = value;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
return cookies;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Extract a session token from Set-Cookie headers of a supertest response.
|
|
792
|
+
* Handles signed cookies (value.signature format) by returning only the value part.
|
|
793
|
+
*/
|
|
794
|
+
static extractSessionToken(response: any, cookieName: string = 'iam.session_token'): null | string {
|
|
795
|
+
const cookies = TestHelper.extractCookies(response);
|
|
796
|
+
const value = cookies[cookieName];
|
|
797
|
+
if (!value) {
|
|
798
|
+
return null;
|
|
799
|
+
}
|
|
800
|
+
// Handle signed cookies: value.signature -> return value
|
|
801
|
+
const dotIndex = value.lastIndexOf('.');
|
|
802
|
+
if (dotIndex > 0 && dotIndex < value.length - 1) {
|
|
803
|
+
return value.substring(0, dotIndex);
|
|
804
|
+
}
|
|
805
|
+
return value;
|
|
806
|
+
}
|
|
807
|
+
|
|
727
808
|
/**
|
|
728
809
|
* Convert JWT into to object
|
|
729
810
|
* @param token
|