@zola_do/authorization 0.1.20 → 0.1.21
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 +158 -150
- package/dist/helper/auth.helper.js +213 -213
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,150 +1,158 @@
|
|
|
1
|
-
# @zola_do/authorization
|
|
2
|
-
|
|
3
|
-
JWT authentication, API key validation, guards, and strategies for NestJS.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
# Install individually
|
|
9
|
-
npm install @zola_do/authorization
|
|
10
|
-
|
|
11
|
-
# Or via meta package
|
|
12
|
-
npm install @zola_do/nestjs-shared
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## Usage
|
|
16
|
-
|
|
17
|
-
### Module Setup
|
|
18
|
-
|
|
19
|
-
```typescript
|
|
20
|
-
import { Module } from '@nestjs/common';
|
|
21
|
-
import { AuthorizationModule } from '@zola_do/authorization';
|
|
22
|
-
|
|
23
|
-
@Module({
|
|
24
|
-
imports: [AuthorizationModule],
|
|
25
|
-
})
|
|
26
|
-
export class AppModule {}
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
By default, `JwtGuard` is registered as a global guard. Protect routes automatically; use `@AllowAnonymous()` for public routes.
|
|
30
|
-
|
|
31
|
-
### Override JwtGuard In AppModule
|
|
32
|
-
|
|
33
|
-
If your app uses a different token payload shape or custom validation logic, override `JwtGuard` in `AppModule`.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
import {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
import {
|
|
51
|
-
import {
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
-
|
|
150
|
-
-
|
|
1
|
+
# @zola_do/authorization
|
|
2
|
+
|
|
3
|
+
JWT authentication, API key validation, guards, and strategies for NestJS.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Install individually
|
|
9
|
+
npm install @zola_do/authorization
|
|
10
|
+
|
|
11
|
+
# Or via meta package
|
|
12
|
+
npm install @zola_do/nestjs-shared
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Module Setup
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { Module } from '@nestjs/common';
|
|
21
|
+
import { AuthorizationModule } from '@zola_do/authorization';
|
|
22
|
+
|
|
23
|
+
@Module({
|
|
24
|
+
imports: [AuthorizationModule],
|
|
25
|
+
})
|
|
26
|
+
export class AppModule {}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
By default, `JwtGuard` is registered as a global guard. Protect routes automatically; use `@AllowAnonymous()` for public routes.
|
|
30
|
+
|
|
31
|
+
### Override JwtGuard In AppModule
|
|
32
|
+
|
|
33
|
+
If your app uses a different token payload shape or custom validation logic, override `JwtGuard` in `AppModule`.
|
|
34
|
+
When `request.user` is already set by your guard, library `JwtGuard`/`OptionalJwtGuard` now skip token parsing and do not overwrite it.
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { Injectable, ExecutionContext } from '@nestjs/common';
|
|
38
|
+
import { JwtGuard } from '@zola_do/authorization';
|
|
39
|
+
|
|
40
|
+
@Injectable()
|
|
41
|
+
export class AppJwtGuard extends JwtGuard {
|
|
42
|
+
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
43
|
+
// Add app-specific token parsing/validation logic here.
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { Module } from '@nestjs/common';
|
|
51
|
+
import { APP_GUARD } from '@nestjs/core';
|
|
52
|
+
import { AuthorizationModule, JwtGuard } from '@zola_do/authorization';
|
|
53
|
+
import { AppJwtGuard } from './guards/app-jwt.guard';
|
|
54
|
+
|
|
55
|
+
@Module({
|
|
56
|
+
imports: [AuthorizationModule],
|
|
57
|
+
providers: [
|
|
58
|
+
AppJwtGuard,
|
|
59
|
+
// Used by controllers that reference @UseGuards(JwtGuard)
|
|
60
|
+
{ provide: JwtGuard, useExisting: AppJwtGuard },
|
|
61
|
+
// Used as global guard
|
|
62
|
+
{ provide: APP_GUARD, useExisting: AppJwtGuard },
|
|
63
|
+
],
|
|
64
|
+
})
|
|
65
|
+
export class AppModule {}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Protected Routes
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { Controller, Get } from '@nestjs/common';
|
|
72
|
+
import { CurrentUser, JwtGuard, UseGuards } from '@zola_do/authorization';
|
|
73
|
+
|
|
74
|
+
@Controller('profile')
|
|
75
|
+
@UseGuards(JwtGuard)
|
|
76
|
+
export class ProfileController {
|
|
77
|
+
@Get()
|
|
78
|
+
getProfile(@CurrentUser() user: any) {
|
|
79
|
+
return user; // Contains JWT payload (id, email, organization, etc.)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Anonymous Routes
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import { AllowAnonymous } from '@zola_do/authorization';
|
|
88
|
+
|
|
89
|
+
@Get('public')
|
|
90
|
+
@AllowAnonymous()
|
|
91
|
+
getPublicData() {
|
|
92
|
+
return { message: 'No auth required' };
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Permission Guards
|
|
97
|
+
|
|
98
|
+
Require specific permissions on routes:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { PermissionsGuard } from '@zola_do/authorization';
|
|
102
|
+
|
|
103
|
+
@Post()
|
|
104
|
+
@UseGuards(JwtGuard, PermissionsGuard('product:create'))
|
|
105
|
+
createProduct(@Body() dto: CreateProductDto) {}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### API Key Guard
|
|
109
|
+
|
|
110
|
+
Protect routes with API key validation:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { ApiKeyGuard } from '@zola_do/authorization';
|
|
114
|
+
|
|
115
|
+
@UseGuards(ApiKeyGuard)
|
|
116
|
+
@Get('api-data')
|
|
117
|
+
getApiData() {}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### AuthHelper
|
|
121
|
+
|
|
122
|
+
Generate and verify tokens:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { AuthHelper } from '@zola_do/authorization';
|
|
126
|
+
|
|
127
|
+
@Injectable()
|
|
128
|
+
export class AuthService {
|
|
129
|
+
constructor(private readonly authHelper: AuthHelper) {}
|
|
130
|
+
|
|
131
|
+
async login(user: User) {
|
|
132
|
+
return this.authHelper.generateTokens(user);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Environment Variables
|
|
138
|
+
|
|
139
|
+
| Variable | Description |
|
|
140
|
+
|----------|-------------|
|
|
141
|
+
| `JWT_ACCESS_TOKEN_SECRET` | Secret for access token signing |
|
|
142
|
+
| `JWT_REFRESH_TOKEN_SECRET` | Secret for refresh token signing |
|
|
143
|
+
| `JWT_ACCESS_TOKEN_EXPIRES` | Access token TTL (e.g. `15m`) |
|
|
144
|
+
| `JWT_REFRESH_TOKEN_EXPIRES` | Refresh token TTL (e.g. `7d`) |
|
|
145
|
+
| `API_KEY` | API key for `ApiKeyGuard` validation |
|
|
146
|
+
|
|
147
|
+
## Exports
|
|
148
|
+
|
|
149
|
+
- **Guards:** `JwtGuard`, `JwtRefreshGuard`, `PermissionsGuard`, `ApiKeyGuard`, `VendorGuard`, `OptionalJwtGuard`, `ThrottlerBehindProxyGuard`
|
|
150
|
+
- **Decorators:** `@CurrentUser()`, `@AllowAnonymous()`
|
|
151
|
+
- **Strategies:** `JwtStrategy`, `JwtRefreshTokenStrategy`
|
|
152
|
+
- **Helpers:** `AuthHelper`
|
|
153
|
+
|
|
154
|
+
## Related Packages
|
|
155
|
+
|
|
156
|
+
- [@zola_do/core](../core) — Shared types and decorators
|
|
157
|
+
- [@zola_do/interceptors](../interceptors) — TenantInterceptor uses `req.user` from JWT
|
|
158
|
+
- [@zola_do/crud](../crud) — Uses JwtGuard and PermissionsGuard
|
|
@@ -65,219 +65,219 @@ let AuthHelper = class AuthHelper {
|
|
|
65
65
|
return randomNumber.toString();
|
|
66
66
|
}
|
|
67
67
|
verifyEmailTemplateForOtp(fullName, username, otp, duration) {
|
|
68
|
-
return `<!DOCTYPE html>
|
|
69
|
-
<html lang="en">
|
|
70
|
-
<head>
|
|
71
|
-
<meta charset="UTF-8" />
|
|
72
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
73
|
-
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
|
74
|
-
<title>Static Template</title>
|
|
75
|
-
|
|
76
|
-
<link
|
|
77
|
-
href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap"
|
|
78
|
-
rel="stylesheet"
|
|
79
|
-
/>
|
|
80
|
-
</head>
|
|
81
|
-
<body
|
|
82
|
-
style="
|
|
83
|
-
margin: 0;
|
|
84
|
-
font-family: 'Poppins', sans-serif;
|
|
85
|
-
background: #ffffff;
|
|
86
|
-
font-size: 14px;
|
|
87
|
-
"
|
|
88
|
-
>
|
|
89
|
-
<header>
|
|
90
|
-
<table style="width: 100%;">
|
|
91
|
-
<tbody>
|
|
92
|
-
<tr style="height: 0;">
|
|
93
|
-
<td>
|
|
94
|
-
</td>
|
|
95
|
-
<td style="text-align: right;">
|
|
96
|
-
<span
|
|
97
|
-
style="font-size: 16px; line-height: 30px; color: #ffffff;"
|
|
98
|
-
>Oct 20, 2023</span
|
|
99
|
-
>
|
|
100
|
-
</td>
|
|
101
|
-
</tr>
|
|
102
|
-
</tbody>
|
|
103
|
-
</table>
|
|
104
|
-
</header>
|
|
105
|
-
|
|
106
|
-
<main>
|
|
107
|
-
<div
|
|
108
|
-
style="
|
|
109
|
-
padding: 92px 30px 115px;
|
|
110
|
-
background: #ffffff;
|
|
111
|
-
border-radius: 30px;
|
|
112
|
-
text-align: center;
|
|
113
|
-
"
|
|
114
|
-
>
|
|
115
|
-
<div style="width: 100%; max-width: 489px; margin: 0 auto;">
|
|
116
|
-
<p
|
|
117
|
-
style="
|
|
118
|
-
margin: 0;
|
|
119
|
-
margin-top: 5px;
|
|
120
|
-
font-size: 16px;
|
|
121
|
-
font-weight: 500;
|
|
122
|
-
"
|
|
123
|
-
>
|
|
124
|
-
Hey ${fullName},
|
|
125
|
-
</p>
|
|
126
|
-
<p
|
|
127
|
-
style="
|
|
128
|
-
margin: 0;
|
|
129
|
-
margin-top: 17px;
|
|
130
|
-
font-weight: 500;
|
|
131
|
-
letter-spacing: 0.56px;
|
|
132
|
-
"
|
|
133
|
-
>
|
|
134
|
-
Use the following OTP
|
|
135
|
-
to complete the procedure to verify your email address. OTP is
|
|
136
|
-
valid for
|
|
137
|
-
<span style="font-weight: 600; color: #1f1f1f;">${duration} minutes</span>.
|
|
138
|
-
Do not share this code with others.
|
|
139
|
-
</p>
|
|
140
|
-
<p
|
|
141
|
-
style="
|
|
142
|
-
margin: 0;
|
|
143
|
-
margin-top: 24px;
|
|
144
|
-
font-size: 16px;
|
|
145
|
-
font-weight: 500;
|
|
146
|
-
"
|
|
147
|
-
>
|
|
148
|
-
Your Username
|
|
149
|
-
</p>
|
|
150
|
-
<p
|
|
151
|
-
style="
|
|
152
|
-
margin: 0;
|
|
153
|
-
margin-top: 17px;
|
|
154
|
-
font-size: 28px;
|
|
155
|
-
font-weight: 600;
|
|
156
|
-
"
|
|
157
|
-
>
|
|
158
|
-
${username}
|
|
159
|
-
</p>
|
|
160
|
-
<p
|
|
161
|
-
style="
|
|
162
|
-
margin: 0;
|
|
163
|
-
margin-top: 17px;
|
|
164
|
-
font-size: 24px;
|
|
165
|
-
font-weight: 500;
|
|
166
|
-
color: #1f1f1f;
|
|
167
|
-
"
|
|
168
|
-
>
|
|
169
|
-
Your OTP
|
|
170
|
-
</p>
|
|
171
|
-
<p
|
|
172
|
-
style="
|
|
173
|
-
font-size: 40px;
|
|
174
|
-
font-weight: 600;
|
|
175
|
-
letter-spacing: 25px;
|
|
176
|
-
color: #fafafa;
|
|
177
|
-
background-color: #0d7801;
|
|
178
|
-
"
|
|
179
|
-
>
|
|
180
|
-
${otp}
|
|
181
|
-
</p>
|
|
182
|
-
</div>
|
|
183
|
-
</div>
|
|
184
|
-
|
|
185
|
-
<p
|
|
186
|
-
style="
|
|
187
|
-
max-width: 400px;
|
|
188
|
-
margin: 0 auto;
|
|
189
|
-
margin-top: 90px;
|
|
190
|
-
text-align: center;
|
|
191
|
-
font-weight: 500;
|
|
192
|
-
color: #8c8c8c;
|
|
193
|
-
"
|
|
194
|
-
>
|
|
195
|
-
Need help? Ask at
|
|
196
|
-
<a
|
|
197
|
-
href="mailto:megp@gmail.com"
|
|
198
|
-
style="color: #499fb6; text-decoration: none;"
|
|
199
|
-
>megp@gmail.com</a
|
|
200
|
-
>
|
|
201
|
-
or visit our
|
|
202
|
-
<a
|
|
203
|
-
href=""
|
|
204
|
-
target="_blank"
|
|
205
|
-
style="color: #499fb6; text-decoration: none;"
|
|
206
|
-
>Help Center</a
|
|
207
|
-
>
|
|
208
|
-
</p>
|
|
209
|
-
</main>
|
|
210
|
-
|
|
211
|
-
<footer
|
|
212
|
-
style="
|
|
213
|
-
width: 100%;
|
|
214
|
-
max-width: 490px;
|
|
215
|
-
margin: 20px auto 0;
|
|
216
|
-
text-align: center;
|
|
217
|
-
border-top: 1px solid #e6ebf1;
|
|
218
|
-
"
|
|
219
|
-
>
|
|
220
|
-
<p
|
|
221
|
-
style="
|
|
222
|
-
margin: 0;
|
|
223
|
-
margin-top: 40px;
|
|
224
|
-
font-size: 16px;
|
|
225
|
-
font-weight: 600;
|
|
226
|
-
color: #434343;
|
|
227
|
-
"
|
|
228
|
-
>
|
|
229
|
-
egp Malawi
|
|
230
|
-
</p>
|
|
231
|
-
<p style="margin: 0; margin-top: 8px; color: #434343;">
|
|
232
|
-
Lilongwe 3, Malawi.
|
|
233
|
-
</p>
|
|
234
|
-
<div style="margin: 0; margin-top: 16px;">
|
|
235
|
-
<a href="" target="_blank" style="display: inline-block;">
|
|
236
|
-
<img
|
|
237
|
-
width="36px"
|
|
238
|
-
alt="Facebook"
|
|
239
|
-
src="https://archisketch-resources.s3.ap-northeast-2.amazonaws.com/vrstyler/1661502815169_682499/email-template-icon-facebook"
|
|
240
|
-
/>
|
|
241
|
-
</a>
|
|
242
|
-
<a
|
|
243
|
-
href=""
|
|
244
|
-
target="_blank"
|
|
245
|
-
style="display: inline-block; margin-left: 8px;"
|
|
246
|
-
>
|
|
247
|
-
<img
|
|
248
|
-
width="36px"
|
|
249
|
-
alt="Instagram"
|
|
250
|
-
src="https://archisketch-resources.s3.ap-northeast-2.amazonaws.com/vrstyler/1661504218208_684135/email-template-icon-instagram"
|
|
251
|
-
/></a>
|
|
252
|
-
<a
|
|
253
|
-
href=""
|
|
254
|
-
target="_blank"
|
|
255
|
-
style="display: inline-block; margin-left: 8px;"
|
|
256
|
-
>
|
|
257
|
-
<img
|
|
258
|
-
width="36px"
|
|
259
|
-
alt="Twitter"
|
|
260
|
-
src="https://archisketch-resources.s3.ap-northeast-2.amazonaws.com/vrstyler/1661503043040_372004/email-template-icon-twitter"
|
|
261
|
-
/>
|
|
262
|
-
</a>
|
|
263
|
-
<a
|
|
264
|
-
href=""
|
|
265
|
-
target="_blank"
|
|
266
|
-
style="display: inline-block; margin-left: 8px;"
|
|
267
|
-
>
|
|
268
|
-
<img
|
|
269
|
-
width="36px"
|
|
270
|
-
alt="Youtube"
|
|
271
|
-
src="https://archisketch-resources.s3.ap-northeast-2.amazonaws.com/vrstyler/1661503195931_210869/email-template-icon-youtube"
|
|
272
|
-
/></a>
|
|
273
|
-
</div>
|
|
274
|
-
<p style="margin: 0; margin-top: 16px; color: #434343;">
|
|
275
|
-
Copyright © 2022 Company. All rights reserved.
|
|
276
|
-
</p>
|
|
277
|
-
</footer>
|
|
278
|
-
</div>
|
|
279
|
-
</body>
|
|
280
|
-
</html>
|
|
68
|
+
return `<!DOCTYPE html>
|
|
69
|
+
<html lang="en">
|
|
70
|
+
<head>
|
|
71
|
+
<meta charset="UTF-8" />
|
|
72
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
73
|
+
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
|
74
|
+
<title>Static Template</title>
|
|
75
|
+
|
|
76
|
+
<link
|
|
77
|
+
href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap"
|
|
78
|
+
rel="stylesheet"
|
|
79
|
+
/>
|
|
80
|
+
</head>
|
|
81
|
+
<body
|
|
82
|
+
style="
|
|
83
|
+
margin: 0;
|
|
84
|
+
font-family: 'Poppins', sans-serif;
|
|
85
|
+
background: #ffffff;
|
|
86
|
+
font-size: 14px;
|
|
87
|
+
"
|
|
88
|
+
>
|
|
89
|
+
<header>
|
|
90
|
+
<table style="width: 100%;">
|
|
91
|
+
<tbody>
|
|
92
|
+
<tr style="height: 0;">
|
|
93
|
+
<td>
|
|
94
|
+
</td>
|
|
95
|
+
<td style="text-align: right;">
|
|
96
|
+
<span
|
|
97
|
+
style="font-size: 16px; line-height: 30px; color: #ffffff;"
|
|
98
|
+
>Oct 20, 2023</span
|
|
99
|
+
>
|
|
100
|
+
</td>
|
|
101
|
+
</tr>
|
|
102
|
+
</tbody>
|
|
103
|
+
</table>
|
|
104
|
+
</header>
|
|
105
|
+
|
|
106
|
+
<main>
|
|
107
|
+
<div
|
|
108
|
+
style="
|
|
109
|
+
padding: 92px 30px 115px;
|
|
110
|
+
background: #ffffff;
|
|
111
|
+
border-radius: 30px;
|
|
112
|
+
text-align: center;
|
|
113
|
+
"
|
|
114
|
+
>
|
|
115
|
+
<div style="width: 100%; max-width: 489px; margin: 0 auto;">
|
|
116
|
+
<p
|
|
117
|
+
style="
|
|
118
|
+
margin: 0;
|
|
119
|
+
margin-top: 5px;
|
|
120
|
+
font-size: 16px;
|
|
121
|
+
font-weight: 500;
|
|
122
|
+
"
|
|
123
|
+
>
|
|
124
|
+
Hey ${fullName},
|
|
125
|
+
</p>
|
|
126
|
+
<p
|
|
127
|
+
style="
|
|
128
|
+
margin: 0;
|
|
129
|
+
margin-top: 17px;
|
|
130
|
+
font-weight: 500;
|
|
131
|
+
letter-spacing: 0.56px;
|
|
132
|
+
"
|
|
133
|
+
>
|
|
134
|
+
Use the following OTP
|
|
135
|
+
to complete the procedure to verify your email address. OTP is
|
|
136
|
+
valid for
|
|
137
|
+
<span style="font-weight: 600; color: #1f1f1f;">${duration} minutes</span>.
|
|
138
|
+
Do not share this code with others.
|
|
139
|
+
</p>
|
|
140
|
+
<p
|
|
141
|
+
style="
|
|
142
|
+
margin: 0;
|
|
143
|
+
margin-top: 24px;
|
|
144
|
+
font-size: 16px;
|
|
145
|
+
font-weight: 500;
|
|
146
|
+
"
|
|
147
|
+
>
|
|
148
|
+
Your Username
|
|
149
|
+
</p>
|
|
150
|
+
<p
|
|
151
|
+
style="
|
|
152
|
+
margin: 0;
|
|
153
|
+
margin-top: 17px;
|
|
154
|
+
font-size: 28px;
|
|
155
|
+
font-weight: 600;
|
|
156
|
+
"
|
|
157
|
+
>
|
|
158
|
+
${username}
|
|
159
|
+
</p>
|
|
160
|
+
<p
|
|
161
|
+
style="
|
|
162
|
+
margin: 0;
|
|
163
|
+
margin-top: 17px;
|
|
164
|
+
font-size: 24px;
|
|
165
|
+
font-weight: 500;
|
|
166
|
+
color: #1f1f1f;
|
|
167
|
+
"
|
|
168
|
+
>
|
|
169
|
+
Your OTP
|
|
170
|
+
</p>
|
|
171
|
+
<p
|
|
172
|
+
style="
|
|
173
|
+
font-size: 40px;
|
|
174
|
+
font-weight: 600;
|
|
175
|
+
letter-spacing: 25px;
|
|
176
|
+
color: #fafafa;
|
|
177
|
+
background-color: #0d7801;
|
|
178
|
+
"
|
|
179
|
+
>
|
|
180
|
+
${otp}
|
|
181
|
+
</p>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<p
|
|
186
|
+
style="
|
|
187
|
+
max-width: 400px;
|
|
188
|
+
margin: 0 auto;
|
|
189
|
+
margin-top: 90px;
|
|
190
|
+
text-align: center;
|
|
191
|
+
font-weight: 500;
|
|
192
|
+
color: #8c8c8c;
|
|
193
|
+
"
|
|
194
|
+
>
|
|
195
|
+
Need help? Ask at
|
|
196
|
+
<a
|
|
197
|
+
href="mailto:megp@gmail.com"
|
|
198
|
+
style="color: #499fb6; text-decoration: none;"
|
|
199
|
+
>megp@gmail.com</a
|
|
200
|
+
>
|
|
201
|
+
or visit our
|
|
202
|
+
<a
|
|
203
|
+
href=""
|
|
204
|
+
target="_blank"
|
|
205
|
+
style="color: #499fb6; text-decoration: none;"
|
|
206
|
+
>Help Center</a
|
|
207
|
+
>
|
|
208
|
+
</p>
|
|
209
|
+
</main>
|
|
210
|
+
|
|
211
|
+
<footer
|
|
212
|
+
style="
|
|
213
|
+
width: 100%;
|
|
214
|
+
max-width: 490px;
|
|
215
|
+
margin: 20px auto 0;
|
|
216
|
+
text-align: center;
|
|
217
|
+
border-top: 1px solid #e6ebf1;
|
|
218
|
+
"
|
|
219
|
+
>
|
|
220
|
+
<p
|
|
221
|
+
style="
|
|
222
|
+
margin: 0;
|
|
223
|
+
margin-top: 40px;
|
|
224
|
+
font-size: 16px;
|
|
225
|
+
font-weight: 600;
|
|
226
|
+
color: #434343;
|
|
227
|
+
"
|
|
228
|
+
>
|
|
229
|
+
egp Malawi
|
|
230
|
+
</p>
|
|
231
|
+
<p style="margin: 0; margin-top: 8px; color: #434343;">
|
|
232
|
+
Lilongwe 3, Malawi.
|
|
233
|
+
</p>
|
|
234
|
+
<div style="margin: 0; margin-top: 16px;">
|
|
235
|
+
<a href="" target="_blank" style="display: inline-block;">
|
|
236
|
+
<img
|
|
237
|
+
width="36px"
|
|
238
|
+
alt="Facebook"
|
|
239
|
+
src="https://archisketch-resources.s3.ap-northeast-2.amazonaws.com/vrstyler/1661502815169_682499/email-template-icon-facebook"
|
|
240
|
+
/>
|
|
241
|
+
</a>
|
|
242
|
+
<a
|
|
243
|
+
href=""
|
|
244
|
+
target="_blank"
|
|
245
|
+
style="display: inline-block; margin-left: 8px;"
|
|
246
|
+
>
|
|
247
|
+
<img
|
|
248
|
+
width="36px"
|
|
249
|
+
alt="Instagram"
|
|
250
|
+
src="https://archisketch-resources.s3.ap-northeast-2.amazonaws.com/vrstyler/1661504218208_684135/email-template-icon-instagram"
|
|
251
|
+
/></a>
|
|
252
|
+
<a
|
|
253
|
+
href=""
|
|
254
|
+
target="_blank"
|
|
255
|
+
style="display: inline-block; margin-left: 8px;"
|
|
256
|
+
>
|
|
257
|
+
<img
|
|
258
|
+
width="36px"
|
|
259
|
+
alt="Twitter"
|
|
260
|
+
src="https://archisketch-resources.s3.ap-northeast-2.amazonaws.com/vrstyler/1661503043040_372004/email-template-icon-twitter"
|
|
261
|
+
/>
|
|
262
|
+
</a>
|
|
263
|
+
<a
|
|
264
|
+
href=""
|
|
265
|
+
target="_blank"
|
|
266
|
+
style="display: inline-block; margin-left: 8px;"
|
|
267
|
+
>
|
|
268
|
+
<img
|
|
269
|
+
width="36px"
|
|
270
|
+
alt="Youtube"
|
|
271
|
+
src="https://archisketch-resources.s3.ap-northeast-2.amazonaws.com/vrstyler/1661503195931_210869/email-template-icon-youtube"
|
|
272
|
+
/></a>
|
|
273
|
+
</div>
|
|
274
|
+
<p style="margin: 0; margin-top: 16px; color: #434343;">
|
|
275
|
+
Copyright © 2022 Company. All rights reserved.
|
|
276
|
+
</p>
|
|
277
|
+
</footer>
|
|
278
|
+
</div>
|
|
279
|
+
</body>
|
|
280
|
+
</html>
|
|
281
281
|
`;
|
|
282
282
|
}
|
|
283
283
|
};
|