proteum 1.0.0-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/.dockerignore +10 -0
- package/Rte.zip +0 -0
- package/cli/app/config.ts +54 -0
- package/cli/app/index.ts +195 -0
- package/cli/bin.js +11 -0
- package/cli/commands/build.ts +34 -0
- package/cli/commands/deploy/app.ts +29 -0
- package/cli/commands/deploy/web.ts +60 -0
- package/cli/commands/dev.ts +109 -0
- package/cli/commands/init.ts +85 -0
- package/cli/compiler/client/identite.ts +72 -0
- package/cli/compiler/client/index.ts +334 -0
- package/cli/compiler/common/babel/index.ts +170 -0
- package/cli/compiler/common/babel/plugins/index.ts +0 -0
- package/cli/compiler/common/babel/plugins/services.ts +579 -0
- package/cli/compiler/common/babel/routes/imports.ts +127 -0
- package/cli/compiler/common/babel/routes/routes.ts +1130 -0
- package/cli/compiler/common/files/autres.ts +39 -0
- package/cli/compiler/common/files/images.ts +35 -0
- package/cli/compiler/common/files/style.ts +78 -0
- package/cli/compiler/common/index.ts +154 -0
- package/cli/compiler/index.ts +532 -0
- package/cli/compiler/server/index.ts +211 -0
- package/cli/index.ts +189 -0
- package/cli/paths.ts +165 -0
- package/cli/print.ts +12 -0
- package/cli/tsconfig.json +38 -0
- package/cli/utils/index.ts +22 -0
- package/cli/utils/keyboard.ts +78 -0
- package/client/app/component.tsx +54 -0
- package/client/app/index.ts +142 -0
- package/client/app/service.ts +34 -0
- package/client/app.tsconfig.json +28 -0
- package/client/components/Button.tsx +298 -0
- package/client/components/Dialog/Manager.tsx +309 -0
- package/client/components/Dialog/card.tsx +208 -0
- package/client/components/Dialog/index.less +151 -0
- package/client/components/Dialog/status.less +176 -0
- package/client/components/Dialog/status.tsx +48 -0
- package/client/components/index.ts +2 -0
- package/client/components/types.d.ts +3 -0
- package/client/data/input.ts +32 -0
- package/client/global.d.ts +5 -0
- package/client/hooks.ts +22 -0
- package/client/index.ts +6 -0
- package/client/pages/_layout/index.less +6 -0
- package/client/pages/_layout/index.tsx +43 -0
- package/client/pages/bug.tsx.old +60 -0
- package/client/pages/useHeader.tsx +50 -0
- package/client/services/captcha/index.ts +67 -0
- package/client/services/router/components/Link.tsx +46 -0
- package/client/services/router/components/Page.tsx +55 -0
- package/client/services/router/components/router.tsx +218 -0
- package/client/services/router/index.tsx +521 -0
- package/client/services/router/request/api.ts +267 -0
- package/client/services/router/request/history.ts +5 -0
- package/client/services/router/request/index.ts +53 -0
- package/client/services/router/request/multipart.ts +147 -0
- package/client/services/router/response/index.tsx +128 -0
- package/client/services/router/response/page.ts +86 -0
- package/client/services/socket/index.ts +147 -0
- package/client/utils/dom.ts +77 -0
- package/common/app/index.ts +9 -0
- package/common/data/chaines/index.ts +54 -0
- package/common/data/dates.ts +179 -0
- package/common/data/markdown.ts +73 -0
- package/common/data/rte/nodes.ts +83 -0
- package/common/data/stats.ts +90 -0
- package/common/errors/index.tsx +326 -0
- package/common/router/index.ts +213 -0
- package/common/router/layouts.ts +93 -0
- package/common/router/register.ts +55 -0
- package/common/router/request/api.ts +77 -0
- package/common/router/request/index.ts +35 -0
- package/common/router/response/index.ts +45 -0
- package/common/router/response/page.ts +128 -0
- package/common/utils/rte.ts +183 -0
- package/common/utils.ts +7 -0
- package/doc/TODO.md +71 -0
- package/doc/front/router.md +27 -0
- package/doc/workspace/workspace.png +0 -0
- package/doc/workspace/workspace2.png +0 -0
- package/doc/workspace/workspace_26.01.22.png +0 -0
- package/package.json +171 -0
- package/server/app/commands.ts +141 -0
- package/server/app/container/config.ts +203 -0
- package/server/app/container/console/index.ts +550 -0
- package/server/app/container/index.ts +137 -0
- package/server/app/index.ts +273 -0
- package/server/app/service/container.ts +88 -0
- package/server/app/service/index.ts +235 -0
- package/server/app.tsconfig.json +28 -0
- package/server/context.ts +4 -0
- package/server/index.ts +4 -0
- package/server/services/auth/index.ts +250 -0
- package/server/services/auth/old.ts +277 -0
- package/server/services/auth/router/index.ts +95 -0
- package/server/services/auth/router/request.ts +54 -0
- package/server/services/auth/router/service.json +6 -0
- package/server/services/auth/service.json +6 -0
- package/server/services/cache/commands.ts +41 -0
- package/server/services/cache/index.ts +297 -0
- package/server/services/cache/service.json +6 -0
- package/server/services/cron/CronTask.ts +86 -0
- package/server/services/cron/index.ts +112 -0
- package/server/services/cron/service.json +6 -0
- package/server/services/disks/driver.ts +103 -0
- package/server/services/disks/drivers/local/index.ts +188 -0
- package/server/services/disks/drivers/local/service.json +6 -0
- package/server/services/disks/drivers/s3/index.ts +301 -0
- package/server/services/disks/drivers/s3/service.json +6 -0
- package/server/services/disks/index.ts +90 -0
- package/server/services/disks/service.json +6 -0
- package/server/services/email/index.ts +188 -0
- package/server/services/email/utils.ts +53 -0
- package/server/services/fetch/index.ts +201 -0
- package/server/services/fetch/service.json +7 -0
- package/server/services/models.7z +0 -0
- package/server/services/prisma/Facet.ts +142 -0
- package/server/services/prisma/index.ts +201 -0
- package/server/services/prisma/service.json +6 -0
- package/server/services/router/http/index.ts +217 -0
- package/server/services/router/http/multipart.ts +102 -0
- package/server/services/router/http/session.ts.old +40 -0
- package/server/services/router/index.ts +801 -0
- package/server/services/router/request/api.ts +87 -0
- package/server/services/router/request/index.ts +184 -0
- package/server/services/router/request/service.ts +21 -0
- package/server/services/router/request/validation/zod.ts +180 -0
- package/server/services/router/response/index.ts +338 -0
- package/server/services/router/response/mask/Filter.ts +323 -0
- package/server/services/router/response/mask/index.ts +60 -0
- package/server/services/router/response/mask/selecteurs.ts +92 -0
- package/server/services/router/response/page/document.tsx +160 -0
- package/server/services/router/response/page/index.tsx +196 -0
- package/server/services/router/service.json +6 -0
- package/server/services/router/service.ts +36 -0
- package/server/services/schema/index.ts +44 -0
- package/server/services/schema/request.ts +49 -0
- package/server/services/schema/router/index.ts +28 -0
- package/server/services/schema/router/service.json +6 -0
- package/server/services/schema/service.json +6 -0
- package/server/services/security/encrypt/aes/index.ts +85 -0
- package/server/services/security/encrypt/aes/service.json +6 -0
- package/server/services/socket/index.ts +162 -0
- package/server/services/socket/scope.ts +226 -0
- package/server/services/socket/service.json +6 -0
- package/server/services_old/SocketClient.ts +92 -0
- package/server/services_old/Token.old.ts +97 -0
- package/server/utils/slug.ts +79 -0
- package/tsconfig.common.json +45 -0
- package/tsconfig.json +3 -0
- package/types/aliases.d.ts +54 -0
- package/types/global/modules.d.ts +49 -0
- package/types/global/utils.d.ts +103 -0
- package/types/icons.d.ts +1 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Npm
|
|
6
|
+
import jwt from 'jsonwebtoken';
|
|
7
|
+
import type express from 'express';
|
|
8
|
+
import type http from 'http';
|
|
9
|
+
|
|
10
|
+
// Core
|
|
11
|
+
import { Application } from '@server/app';
|
|
12
|
+
import Service from '@server/app/service';
|
|
13
|
+
import type { TAnyRouter } from '@server/services/router';
|
|
14
|
+
import {
|
|
15
|
+
default as Router, Request as ServerRequest,
|
|
16
|
+
} from '@server/services/router';
|
|
17
|
+
import { InputError, AuthRequired, Forbidden } from '@common/errors';
|
|
18
|
+
|
|
19
|
+
/*----------------------------------
|
|
20
|
+
- TYPES
|
|
21
|
+
----------------------------------*/
|
|
22
|
+
|
|
23
|
+
export type TUserRole = typeof UserRoles[number]
|
|
24
|
+
|
|
25
|
+
export type THttpRequest = express.Request | http.IncomingMessage;
|
|
26
|
+
|
|
27
|
+
/*----------------------------------
|
|
28
|
+
- CONFIG
|
|
29
|
+
----------------------------------*/
|
|
30
|
+
|
|
31
|
+
const LogPrefix = '[auth]'
|
|
32
|
+
|
|
33
|
+
export const UserRoles = ['USER', 'ADMIN', 'TEST', 'DEV'] as const
|
|
34
|
+
|
|
35
|
+
/*----------------------------------
|
|
36
|
+
- SERVICE CONVIG
|
|
37
|
+
----------------------------------*/
|
|
38
|
+
|
|
39
|
+
export type TConfig = {
|
|
40
|
+
debug: boolean,
|
|
41
|
+
logoutUrl: string,
|
|
42
|
+
jwt: {
|
|
43
|
+
// 2048 bits
|
|
44
|
+
key: string,
|
|
45
|
+
expiration: number,
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type THooks = {
|
|
50
|
+
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type TBasicUser = {
|
|
54
|
+
type: string,
|
|
55
|
+
name: string | null,
|
|
56
|
+
email: string,
|
|
57
|
+
roles: string[]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type TBasicJwtSession = (
|
|
61
|
+
{ apiKey: string }
|
|
62
|
+
|
|
|
63
|
+
{ email: string }
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
/*----------------------------------
|
|
67
|
+
- SERVICE
|
|
68
|
+
----------------------------------*/
|
|
69
|
+
export default abstract class AuthService<
|
|
70
|
+
TUser extends TBasicUser,
|
|
71
|
+
TApplication extends Application,
|
|
72
|
+
TJwtSession extends TBasicJwtSession = TBasicJwtSession,
|
|
73
|
+
TRequest extends ServerRequest<TAnyRouter> = ServerRequest<TAnyRouter>,
|
|
74
|
+
> extends Service<TConfig, THooks, TApplication, TApplication> {
|
|
75
|
+
|
|
76
|
+
//public abstract login( ...args: any[] ): Promise<{ user: TUser, token: string }>;
|
|
77
|
+
public abstract decodeSession( jwt: TJwtSession, req: THttpRequest ): Promise<TUser | null>;
|
|
78
|
+
|
|
79
|
+
// https://beeceptor.com/docs/concepts/authorization-header/#examples
|
|
80
|
+
public async decode( req: THttpRequest, withData: true ): Promise<TUser | null>;
|
|
81
|
+
public async decode( req: THttpRequest, withData?: false ): Promise<TJwtSession | null>;
|
|
82
|
+
public async decode( req: THttpRequest, withData: boolean = false ): Promise<TJwtSession | TUser | null> {
|
|
83
|
+
|
|
84
|
+
this.config.debug && console.log(LogPrefix, 'Decode:', { cookie: req.cookies['authorization'] });
|
|
85
|
+
|
|
86
|
+
// Get auth token
|
|
87
|
+
const authMethod = this.getAuthMethod(req);
|
|
88
|
+
if (authMethod === null)
|
|
89
|
+
return null;
|
|
90
|
+
const { tokenType, token } = authMethod;
|
|
91
|
+
|
|
92
|
+
// Get auth session
|
|
93
|
+
const session = this.getAuthSession(tokenType, token);
|
|
94
|
+
if (session === null)
|
|
95
|
+
return null;
|
|
96
|
+
|
|
97
|
+
// Return email only
|
|
98
|
+
if (!withData) {
|
|
99
|
+
this.config.debug && console.log(LogPrefix, `Auth user successfull. Return email only`);
|
|
100
|
+
return session;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Deserialize full user data
|
|
104
|
+
this.config.debug && console.log(LogPrefix, `Deserialize user`, session);
|
|
105
|
+
const user = await this.decodeSession(session, req);
|
|
106
|
+
if (user === null)
|
|
107
|
+
return null;
|
|
108
|
+
|
|
109
|
+
this.config.debug && console.log(LogPrefix, `Deserialized user:`, user.name);
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
...user,
|
|
113
|
+
_token: token
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private getAuthMethod( req: THttpRequest ): null | { token: string, tokenType?: string } {
|
|
118
|
+
|
|
119
|
+
let token: string | undefined;
|
|
120
|
+
let tokenType: string | undefined;
|
|
121
|
+
if (typeof req.headers['authorization'] === 'string') {
|
|
122
|
+
|
|
123
|
+
([ tokenType, token ] = req.headers['authorization'].split(' '));
|
|
124
|
+
|
|
125
|
+
} else if (('cookies' in req) && typeof req.cookies['authorization'] === 'string') {
|
|
126
|
+
|
|
127
|
+
token = req.cookies['authorization'];
|
|
128
|
+
tokenType = 'Bearer';
|
|
129
|
+
|
|
130
|
+
} else
|
|
131
|
+
return null;
|
|
132
|
+
|
|
133
|
+
if (token === undefined)
|
|
134
|
+
return null;
|
|
135
|
+
|
|
136
|
+
return { tokenType, token };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private getAuthSession( tokenType: string | undefined, token: string ): TJwtSession | null {
|
|
140
|
+
|
|
141
|
+
let session: TJwtSession;
|
|
142
|
+
|
|
143
|
+
// API Key
|
|
144
|
+
if (tokenType === 'Apikey') {
|
|
145
|
+
|
|
146
|
+
const [accountType] = token.split('-');
|
|
147
|
+
|
|
148
|
+
this.config.debug && console.log(LogPrefix, `Auth via API Key`, token);
|
|
149
|
+
session = { accountType, apiKey: token } as TJwtSession;
|
|
150
|
+
|
|
151
|
+
// JWT
|
|
152
|
+
} else if (tokenType === 'Bearer') {
|
|
153
|
+
this.config.debug && console.log(LogPrefix, `Auth via JWT token`, token);
|
|
154
|
+
try {
|
|
155
|
+
session = jwt.verify(token, this.config.jwt.key, {
|
|
156
|
+
maxAge: this.config.jwt.expiration
|
|
157
|
+
});
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.warn(LogPrefix, "Failed to decode jwt token:", token);
|
|
160
|
+
return null;
|
|
161
|
+
//throw new Forbidden(`The JWT token provided in the Authorization header is invalid`);
|
|
162
|
+
}
|
|
163
|
+
} else
|
|
164
|
+
return null;
|
|
165
|
+
//throw new InputError(`The authorization scheme provided in the Authorization header is unsupported.`);
|
|
166
|
+
|
|
167
|
+
return session;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
public createSession( session: TJwtSession, request: TRequest ): string {
|
|
171
|
+
|
|
172
|
+
this.config.debug && console.info(LogPrefix, `Creating new session:`, session);
|
|
173
|
+
|
|
174
|
+
const token = jwt.sign(session, this.config.jwt.key);
|
|
175
|
+
|
|
176
|
+
this.config.debug && console.info(LogPrefix, `Generated JWT token for session:` + token);
|
|
177
|
+
|
|
178
|
+
request.res.cookie('authorization', token, {
|
|
179
|
+
maxAge: this.config.jwt.expiration,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return token;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
public logout( request: TRequest ) {
|
|
186
|
+
|
|
187
|
+
const user = request.user;
|
|
188
|
+
if (!user) return;
|
|
189
|
+
|
|
190
|
+
this.config.debug && console.info(LogPrefix, `Logout ${user.name}`);
|
|
191
|
+
request.res.clearCookie('authorization');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
public check(
|
|
195
|
+
request: TRequest,
|
|
196
|
+
role: TUserRole,
|
|
197
|
+
motivation?: string,
|
|
198
|
+
dataForDebug?: { [key: string]: any }
|
|
199
|
+
): TUser;
|
|
200
|
+
|
|
201
|
+
public check(
|
|
202
|
+
request: TRequest,
|
|
203
|
+
role: false,
|
|
204
|
+
motivation?: string,
|
|
205
|
+
dataForDebug?: { [key: string]: any }
|
|
206
|
+
): null;
|
|
207
|
+
|
|
208
|
+
public check(
|
|
209
|
+
request: TRequest,
|
|
210
|
+
role: TUserRole | false = 'USER',
|
|
211
|
+
motivation?: string,
|
|
212
|
+
dataForDebug?: { [key: string]: any }
|
|
213
|
+
): TUser | null {
|
|
214
|
+
|
|
215
|
+
const user = request.user;
|
|
216
|
+
|
|
217
|
+
this.config.debug && console.warn(LogPrefix, `Check auth, role = ${role}. Current user =`, user?.name, motivation);
|
|
218
|
+
|
|
219
|
+
if (user === undefined) {
|
|
220
|
+
|
|
221
|
+
throw new Error(`request.user has not been decoded.`);
|
|
222
|
+
|
|
223
|
+
// Shoudln't be logged
|
|
224
|
+
} else if (role === false) {
|
|
225
|
+
|
|
226
|
+
return user;
|
|
227
|
+
|
|
228
|
+
// Not connected
|
|
229
|
+
} else if (user === null) {
|
|
230
|
+
|
|
231
|
+
console.warn(LogPrefix, "Refusé pour anonyme (" + request.ip + ")");
|
|
232
|
+
throw new AuthRequired('Please login to continue', motivation, dataForDebug);
|
|
233
|
+
|
|
234
|
+
// Insufficient permissions
|
|
235
|
+
} else if (!user.roles.includes(role)) {
|
|
236
|
+
|
|
237
|
+
console.warn(LogPrefix, "Refusé: " + role + " pour " + user.name + " (" + (user.roles || 'role inconnu') + ")");
|
|
238
|
+
|
|
239
|
+
throw new Forbidden("You do not have sufficient permissions to access this resource.");
|
|
240
|
+
|
|
241
|
+
} else {
|
|
242
|
+
|
|
243
|
+
this.config.debug && console.warn(LogPrefix, "Autorisé " + role + " pour " + user.name + " (" + user.roles + ")");
|
|
244
|
+
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return user;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
|
|
2
|
+
import md5 from 'md5';
|
|
3
|
+
import { OAuth2Client, LoginTicket } from 'google-auth-library';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
type AuthResponse = {
|
|
8
|
+
token: string,
|
|
9
|
+
redirect: string,
|
|
10
|
+
user: User
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default class {
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
protected async ready() {
|
|
17
|
+
|
|
18
|
+
// Google auth client
|
|
19
|
+
if (this.config.google) {
|
|
20
|
+
|
|
21
|
+
const httpConfig = this.app.http.publicUrl;
|
|
22
|
+
|
|
23
|
+
this.googleClient = new OAuth2Client(
|
|
24
|
+
this.config.google.web.clientId, // Google Client ID
|
|
25
|
+
this.config.google.web.secret, // Private key
|
|
26
|
+
httpConfig + "/auth/google/response" // Redirect url
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
private googleClient: OAuth2Client | undefined;
|
|
33
|
+
|
|
34
|
+
public async FromGoogle(request: ServerRequest): Promise<string> {
|
|
35
|
+
|
|
36
|
+
if (!this.googleClient)
|
|
37
|
+
throw new Forbidden(`Authentication method disabled.`);
|
|
38
|
+
|
|
39
|
+
// Register start time, so we can determine the signup time to display
|
|
40
|
+
request.response?.cookie('signupstart', Date.now());
|
|
41
|
+
|
|
42
|
+
// Google auth doesn't support local env
|
|
43
|
+
// So we simulate that google sent a response with the user email
|
|
44
|
+
if (app.env.name === 'local') {
|
|
45
|
+
const { redirect } = await this.Auth("tbsoftwares23@gmail.com", request, true);
|
|
46
|
+
return redirect;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return this.googleClient.generateAuthUrl({
|
|
50
|
+
access_type: 'offline',
|
|
51
|
+
scope: [
|
|
52
|
+
"email", "profile"
|
|
53
|
+
]
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public async GoogleResponse(
|
|
59
|
+
type: 'code' | 'token',
|
|
60
|
+
codeOrToken: string | undefined,
|
|
61
|
+
request: ServerRequest,
|
|
62
|
+
): Promise<AuthResponse> {
|
|
63
|
+
|
|
64
|
+
const googleConfig = this.config.google;
|
|
65
|
+
if (!this.googleClient || !googleConfig)
|
|
66
|
+
throw new Forbidden(`Authentication method disabled.`);
|
|
67
|
+
|
|
68
|
+
if (codeOrToken === undefined)
|
|
69
|
+
throw new Forbidden("Bad code / token");
|
|
70
|
+
|
|
71
|
+
if (type === 'code') {
|
|
72
|
+
const r = await this.googleClient.getToken(codeOrToken);
|
|
73
|
+
return this.GoogleResponse('token', r.tokens.id_token, request);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.config.debug && console.log(LogPrefix, "Auth via google", googleConfig);
|
|
77
|
+
|
|
78
|
+
let ticket: LoginTicket;
|
|
79
|
+
try {
|
|
80
|
+
ticket = await this.googleClient.verifyIdToken({
|
|
81
|
+
idToken: codeOrToken,
|
|
82
|
+
audience: [
|
|
83
|
+
googleConfig.web.clientId,
|
|
84
|
+
googleConfig.android.clientId,
|
|
85
|
+
]
|
|
86
|
+
});
|
|
87
|
+
} catch (error) {
|
|
88
|
+
throw new Forbidden(`Google denied your login attempt: ` + error.message + `. If you don't think it's normal, please contact us.`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const payload = ticket.getPayload();
|
|
92
|
+
if (payload === undefined)
|
|
93
|
+
throw new Forbidden("Invalid payload");
|
|
94
|
+
const { email, sub: google_id } = payload;
|
|
95
|
+
|
|
96
|
+
if (email === undefined)
|
|
97
|
+
throw new Forbidden("Unable to get your email address from the Google sign-in.");
|
|
98
|
+
|
|
99
|
+
return await this.Auth(email, request, true);
|
|
100
|
+
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Login u
|
|
105
|
+
* @param email Email that identifies the account to log in
|
|
106
|
+
* @param request Used to call security-related features
|
|
107
|
+
* @param canPass true when from 3rd party auth, we're sure the email is owned bu the current user
|
|
108
|
+
* @param userInfo User field to update (eg: lastLogin)
|
|
109
|
+
* @returns
|
|
110
|
+
*/
|
|
111
|
+
public async Auth(
|
|
112
|
+
email: string,
|
|
113
|
+
request: ServerRequest,
|
|
114
|
+
canPass: boolean = false,
|
|
115
|
+
userInfo: Partial<User> = {}
|
|
116
|
+
): Promise<AuthResponse> {
|
|
117
|
+
|
|
118
|
+
let user = await this.getData('email = ' + this.sql.esc(email));
|
|
119
|
+
let ip: IP;
|
|
120
|
+
let redirect: string;
|
|
121
|
+
if (!user) { // Signup
|
|
122
|
+
|
|
123
|
+
ip = await request.detect.botsAndMultiaccount();
|
|
124
|
+
|
|
125
|
+
// Create user
|
|
126
|
+
({ user, redirect } = await this.Signup(email, request));
|
|
127
|
+
|
|
128
|
+
} else if (!canPass) { // Send login email
|
|
129
|
+
|
|
130
|
+
throw new Forbidden("This option is not available");
|
|
131
|
+
|
|
132
|
+
// If the current IP was used to connect to another account that the current
|
|
133
|
+
ip = await request.detect.botsAndMultiaccount(user.email);
|
|
134
|
+
|
|
135
|
+
// Send email with link to /auth/token (expiration 5 min)
|
|
136
|
+
|
|
137
|
+
// Route /auth/:token: check si même ip, même device
|
|
138
|
+
|
|
139
|
+
} else { // Login
|
|
140
|
+
|
|
141
|
+
redirect = config.logoutUrl;
|
|
142
|
+
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/*await this.sql`
|
|
146
|
+
INSERT INTO UserLogin SET
|
|
147
|
+
user = ${user.email},
|
|
148
|
+
date = NOW(),
|
|
149
|
+
ip = ${request.ip},
|
|
150
|
+
device = ${request.deviceString()}
|
|
151
|
+
`.run();*/
|
|
152
|
+
|
|
153
|
+
const token = request.auth.login(user.email);
|
|
154
|
+
|
|
155
|
+
return { token, user, redirect };
|
|
156
|
+
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
public async Signup(email: string, request: ServerRequest, moreInfos: Partial<User> = {}) {
|
|
160
|
+
|
|
161
|
+
let username = email.split('@')[0];
|
|
162
|
+
|
|
163
|
+
// Prefix username if alreasy existing
|
|
164
|
+
const duplicates = await this.sql`
|
|
165
|
+
FROM User
|
|
166
|
+
WHERE name REGEXP CONCAT('^', ${username}, '[0-9]*$');
|
|
167
|
+
`.count();
|
|
168
|
+
|
|
169
|
+
if (duplicates !== 0)
|
|
170
|
+
username += duplicates;
|
|
171
|
+
|
|
172
|
+
const user: Partial<User> = {
|
|
173
|
+
email,
|
|
174
|
+
emailHash: md5(email),
|
|
175
|
+
name: username,
|
|
176
|
+
referrer: request.cookies.r,
|
|
177
|
+
utm: request.cookies.utm,
|
|
178
|
+
...moreInfos
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Hook
|
|
182
|
+
if (this.beforeSignup !== undefined)
|
|
183
|
+
await this.beforeSignup( user );
|
|
184
|
+
|
|
185
|
+
// Referrer
|
|
186
|
+
if (user.referrer !== undefined) {
|
|
187
|
+
|
|
188
|
+
const refExists = await this.sql`FROM User WHERE name = ${user.referrer}`.exists();
|
|
189
|
+
if (!refExists)
|
|
190
|
+
user.referrer = undefined;
|
|
191
|
+
else {
|
|
192
|
+
|
|
193
|
+
await this.sql.upsert('UserStats', {
|
|
194
|
+
user: user.referrer,
|
|
195
|
+
date: new Date,
|
|
196
|
+
refSignups: 1
|
|
197
|
+
}, ['refSignups'], {
|
|
198
|
+
upsertMode: 'increment'
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Create user
|
|
204
|
+
await this.sql.insert('User', user);
|
|
205
|
+
await this.sql.update('logs.IP', { user_name: username }, { address: request.ip });
|
|
206
|
+
|
|
207
|
+
// Hook
|
|
208
|
+
let redirect: string = '/';
|
|
209
|
+
if (this.afterSignup !== undefined)
|
|
210
|
+
({ redirect } = await this.afterSignup(user));
|
|
211
|
+
|
|
212
|
+
// TODO: download user avatar from gravatar
|
|
213
|
+
// remove user.emailHash
|
|
214
|
+
|
|
215
|
+
// Notif email
|
|
216
|
+
await this.email.send({
|
|
217
|
+
to: app.identity.author.email,
|
|
218
|
+
subject: app.identity.name + ": New User",
|
|
219
|
+
html: JSON.stringify(user)
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// TODO
|
|
223
|
+
// this.hook.signup(user);
|
|
224
|
+
//Achievements.activity(user, 'just signed up', null);
|
|
225
|
+
|
|
226
|
+
// Instanciation
|
|
227
|
+
return { user, redirect };
|
|
228
|
+
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
public async setReferrer( referrer: unknown, { user, response, cookies, req, detect, request }: ServerRequest) {
|
|
232
|
+
|
|
233
|
+
if (!response || user)
|
|
234
|
+
return;
|
|
235
|
+
|
|
236
|
+
// Source tracking
|
|
237
|
+
if (!cookies.utm) {
|
|
238
|
+
|
|
239
|
+
const data = req.query;
|
|
240
|
+
|
|
241
|
+
const utm = [data.utm_medium, data.utm_source, data.utm_campaign]
|
|
242
|
+
.filter(a => a !== undefined);
|
|
243
|
+
|
|
244
|
+
response.cookie('utm', utm.join(','));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Referral program
|
|
248
|
+
if (cookies.r === undefined && typeof referrer === "string") {
|
|
249
|
+
|
|
250
|
+
const conflict = await detect.conflictOfInterest( referrer, request );
|
|
251
|
+
if (conflict)
|
|
252
|
+
return;
|
|
253
|
+
|
|
254
|
+
// Check if user exists
|
|
255
|
+
const referrerExists = await this.sql`FROM User WHERE name = ${referrer}`.count();
|
|
256
|
+
if (referrerExists === 0)
|
|
257
|
+
return;
|
|
258
|
+
|
|
259
|
+
// await secutity.detectCheatAttempts();
|
|
260
|
+
// => Check IP (reprendre code existant)
|
|
261
|
+
// => Check si IP pas déjà utilisée par un autre membre (1 compte par IP)
|
|
262
|
+
|
|
263
|
+
// Tracking cookie for signup
|
|
264
|
+
response.cookie('r', referrer);
|
|
265
|
+
|
|
266
|
+
// Count the clic
|
|
267
|
+
await this.sql.upsert('UserStats', {
|
|
268
|
+
user: referrer,
|
|
269
|
+
date: new Date,
|
|
270
|
+
refClics: 1
|
|
271
|
+
}, ['refClics'], {
|
|
272
|
+
upsertMode: 'increment'
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Npm
|
|
6
|
+
|
|
7
|
+
// Core
|
|
8
|
+
import {
|
|
9
|
+
default as Router, Request as ServerRequest, Response as ServerResponse, TAnyRoute,
|
|
10
|
+
RouterService, TAnyRouter
|
|
11
|
+
} from '@server/services/router';
|
|
12
|
+
|
|
13
|
+
import type { Application } from '@server/app';
|
|
14
|
+
|
|
15
|
+
import type { TRouterServiceArgs } from '@server/services/router/service';
|
|
16
|
+
|
|
17
|
+
// Specific
|
|
18
|
+
import type { default as UsersService, TUserRole, TBasicUser } from '..';
|
|
19
|
+
import UsersRequestService from './request';
|
|
20
|
+
|
|
21
|
+
/*----------------------------------
|
|
22
|
+
- TYPES
|
|
23
|
+
----------------------------------*/
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
/*----------------------------------
|
|
27
|
+
- CONFIG
|
|
28
|
+
----------------------------------*/
|
|
29
|
+
|
|
30
|
+
const LogPrefix = '[router][auth]';
|
|
31
|
+
|
|
32
|
+
/*----------------------------------
|
|
33
|
+
- SERVICE
|
|
34
|
+
----------------------------------*/
|
|
35
|
+
export default class AuthenticationRouterService<
|
|
36
|
+
TApplication extends Application = Application,
|
|
37
|
+
TUser extends TBasicUser = TApplication["app"]["userType"],
|
|
38
|
+
TRouter extends TAnyRouter = TAnyRouter,
|
|
39
|
+
TRequest extends ServerRequest<TRouter> = ServerRequest<TRouter>,
|
|
40
|
+
> extends RouterService<{}, TRouter> {
|
|
41
|
+
|
|
42
|
+
/*----------------------------------
|
|
43
|
+
- LIFECYCLE
|
|
44
|
+
----------------------------------*/
|
|
45
|
+
|
|
46
|
+
public users: UsersService<TUser, Application>;
|
|
47
|
+
|
|
48
|
+
public constructor( getConfig: TRouterServiceArgs[0], app: TApplication ) {
|
|
49
|
+
super( getConfig, app );
|
|
50
|
+
|
|
51
|
+
this.users = this.config.users;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
protected async ready() {
|
|
55
|
+
|
|
56
|
+
// Decode current user
|
|
57
|
+
this.parent.on('request', async (request: TRequest) => {
|
|
58
|
+
|
|
59
|
+
// TODO: Typings. (context.user ?)
|
|
60
|
+
const decoded = await this.users.decode( request.req, true);
|
|
61
|
+
|
|
62
|
+
request.user = decoded || null;
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// Check route permissions
|
|
66
|
+
this.parent.on('resolved', async (
|
|
67
|
+
route: TAnyRoute,
|
|
68
|
+
request: TRequest,
|
|
69
|
+
response: ServerResponse<TRouter>
|
|
70
|
+
) => {
|
|
71
|
+
|
|
72
|
+
if (route.options.auth !== undefined) {
|
|
73
|
+
|
|
74
|
+
// Basic auth check
|
|
75
|
+
this.users.check(request, route.options.auth);
|
|
76
|
+
|
|
77
|
+
// Redirect to logged page
|
|
78
|
+
if (route.options.auth === false && request.user && route.options.redirectLogged)
|
|
79
|
+
response.redirect(route.options.redirectLogged);
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
protected async shutdown() {
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/*----------------------------------
|
|
89
|
+
- ROUTER SERVICE LIFECYCLE
|
|
90
|
+
----------------------------------*/
|
|
91
|
+
|
|
92
|
+
public requestService( request: TRequest ): UsersRequestService<TRouter, TUser> {
|
|
93
|
+
return new UsersRequestService( request, this );
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Npm
|
|
6
|
+
import jwt from 'jsonwebtoken';
|
|
7
|
+
|
|
8
|
+
// Core
|
|
9
|
+
import type { default as Router, Request as ServerRequest, TAnyRouter } from '@server/services/router';
|
|
10
|
+
import RequestService from '@server/services/router/request/service';
|
|
11
|
+
import { InputError, AuthRequired, Forbidden } from '@common/errors';
|
|
12
|
+
|
|
13
|
+
// Specific
|
|
14
|
+
import type AuthenticationRouterService from '.';
|
|
15
|
+
import type { default as UsersManagementService, TUserRole } from '..';
|
|
16
|
+
|
|
17
|
+
// Types
|
|
18
|
+
import type { TBasicUser } from '@server/services/auth';
|
|
19
|
+
|
|
20
|
+
/*----------------------------------
|
|
21
|
+
- TYPES
|
|
22
|
+
----------------------------------*/
|
|
23
|
+
|
|
24
|
+
/*----------------------------------
|
|
25
|
+
- MODULE
|
|
26
|
+
----------------------------------*/
|
|
27
|
+
export default class UsersRequestService<
|
|
28
|
+
TRouter extends TAnyRouter,
|
|
29
|
+
TUser extends TBasicUser
|
|
30
|
+
> extends RequestService {
|
|
31
|
+
|
|
32
|
+
public constructor(
|
|
33
|
+
request: ServerRequest<TRouter>,
|
|
34
|
+
public auth: AuthenticationRouterService,
|
|
35
|
+
public users = auth.users,
|
|
36
|
+
) {
|
|
37
|
+
super(request);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public login( email: string ) {
|
|
41
|
+
return this.users.login( this.request, email );
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public logout() {
|
|
45
|
+
return this.users.logout( this.request );
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// TODO: return user type according to entity
|
|
49
|
+
public check(role: TUserRole, motivation?: string, dataForDebug?: {}): TUser;
|
|
50
|
+
public check(role: false, motivation?: string, dataForDebug?: {}): null;
|
|
51
|
+
public check(role: TUserRole | boolean = 'USER', motivation?: string, dataForDebug?: {}): TUser | null {
|
|
52
|
+
return this.users.check( this.request, role, motivation, dataForDebug );
|
|
53
|
+
}
|
|
54
|
+
}
|