5htp-core 0.2.6-4 → 0.2.6-5

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "5htp-core",
3
3
  "description": "Convenient TypeScript framework designed for Performance and Productivity.",
4
- "version": "0.2.6-4",
4
+ "version": "0.2.6-5",
5
5
  "author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
6
6
  "repository": "git://github.com/gaetanlegac/5htp-core.git",
7
7
  "license": "MIT",
@@ -133,7 +133,9 @@ export default class ServerResponse<
133
133
  for (const serviceName in this.router.services) {
134
134
 
135
135
  const routerService = this.router.services[serviceName];
136
- contextServices[ serviceName ] = routerService.requestService( this.request );
136
+ const requestService = routerService.requestService( this.request );
137
+ if (requestService !== null)
138
+ contextServices[ serviceName ] = requestService;
137
139
 
138
140
  }
139
141
 
@@ -19,10 +19,10 @@ import type Page from '.';
19
19
  /*----------------------------------
20
20
  - SERVICE
21
21
  ----------------------------------*/
22
- export default class DocumentRenderer {
22
+ export default class DocumentRenderer<TRouter extends Router> {
23
23
 
24
24
  public constructor(
25
- public router: Router,
25
+ public router: TRouter,
26
26
  public app = router.app
27
27
  ) {
28
28
 
@@ -55,7 +55,7 @@ export default class DocumentRenderer {
55
55
  );
56
56
  }
57
57
 
58
- public async page( html: string, page: Page, response: ServerResponse<Router> ) {
58
+ public async page( html: string, page: Page, response: ServerResponse<TRouter> ) {
59
59
 
60
60
  const fullUrl = this.router.http.publicUrl + response.request.path;
61
61
 
@@ -133,7 +133,7 @@ export default class DocumentRenderer {
133
133
  </>
134
134
  }
135
135
 
136
- private styles( page ) {
136
+ private styles( page: Page ) {
137
137
  return <>
138
138
  <link rel="stylesheet" type="text/css" href="/public/icons.css" />
139
139
  <link rel="preload" href="/public/client.css" as="style" />
@@ -151,7 +151,7 @@ export default class DocumentRenderer {
151
151
  </>
152
152
  }
153
153
 
154
- private async scripts( response, page ) {
154
+ private async scripts( response: ServerResponse<TRouter>, page: Page ) {
155
155
 
156
156
  const context = safeStringify( response.forSsr(page) );
157
157
 
@@ -174,21 +174,9 @@ export default class DocumentRenderer {
174
174
  <link rel="preload" href={script.url} as="script" />
175
175
  <script type="text/javascript" src={script.url} {...script.attrs || {}} />
176
176
  </> : <>
177
- <script type="text/javascript" {...script.attrs || {}} id={script.id} dangerouslySetInnerHTML={{ __html: script.inline }} />
177
+ <script type="text/javascript" {...script.attrs || {}} id={script.id}
178
+ dangerouslySetInnerHTML={{ __html: script.inline }} />
178
179
  </>)}
179
-
180
- {/* Initialize GTM & GA for pagechange events */}
181
- {/* TODO: append via the metrics module */}
182
- {/*<script async src={"https://www.googletagmanager.com/gtag/js?id=" + this.app.config.tracking.ga.pub}></script>
183
- <script dangerouslySetInnerHTML={{ __html: `
184
- window.dataLayer = window.dataLayer || [];
185
- function gtag(){dataLayer.push(arguments);}
186
- gtag('js', new Date());
187
-
188
- gtag('config', '${this.app.config.tracking.ga.pub}', {
189
- send_page_view: false
190
- });
191
- `}} />*/}
192
180
  </>
193
181
  }
194
182
  }
@@ -43,6 +43,6 @@ export default abstract class RouterService<TRouter extends Router = Router> {
43
43
 
44
44
  public abstract register(): Promise<void>;
45
45
 
46
- public abstract requestService( request: ServerRequest<TRouter> ): RequestService;
46
+ public abstract requestService( request: ServerRequest<TRouter> ): RequestService | null;
47
47
 
48
48
  }
@@ -1,37 +0,0 @@
1
- /*----------------------------------
2
- - DEPENDANCES
3
- ----------------------------------*/
4
-
5
- // Npm
6
-
7
- // Core
8
- import type ClientApplication from '@client/app';
9
-
10
- /*----------------------------------
11
- - TYPES
12
- ----------------------------------*/
13
-
14
-
15
-
16
- /*----------------------------------
17
- - SERVICE
18
- ----------------------------------*/
19
- export default class ClientMetrics {
20
-
21
- public constructor( public app: ClientApplication ) {
22
-
23
- }
24
-
25
- public start() {
26
-
27
- }
28
-
29
- // Tracking
30
- public event( name: string, params?: object ) {
31
- if (!window.gtag) return;
32
- if (name === 'pageview')
33
- window.gtag('send', name);
34
- else
35
- window.gtag('event', name, params);
36
- }
37
- }
@@ -1,109 +0,0 @@
1
- /*----------------------------------
2
- - DEPENDANCES
3
- ----------------------------------*/
4
-
5
- // Npm
6
- import got from 'got';
7
-
8
- // Core
9
- import { Forbidden } from '@common/errors';
10
- import ServerRequest from '../router/request';
11
-
12
- // App
13
- import app from '@server/app';
14
-
15
- /*----------------------------------
16
- - TYPES
17
- ----------------------------------*/
18
-
19
- /*----------------------------------
20
- - MODULE
21
- ----------------------------------*/
22
- export default class ProtectService {
23
-
24
- private tracking: TrackerService;
25
-
26
- public constructor( private request: ServerRequest ) {
27
-
28
- this.tracking = request.tracking;
29
-
30
- }
31
-
32
- public async bots(): Promise<IP> {
33
-
34
- // Récupération et varifications informations ip
35
- const ip = await this.tracking.checkIP();
36
-
37
- // Captcha
38
- /*if (this.request.method === 'POST')
39
- await this.captcha(this.request.data.captcha);*/
40
-
41
- return ip;
42
- }
43
-
44
- public async multiaccount(ip: IP, whitelist?: string) {
45
-
46
- // DON'T USE IT
47
- // People can't try the app when another user recommands it IRL
48
- // TODO: find another way to detect / avoid multi account
49
- // Ex: Force login with Google only
50
-
51
- // If IP Address already used by another account
52
- /*const username = whitelist || this.request.user?.name;
53
- if (ip.user_name !== undefined && ip.user_name !== null && (username === undefined || username !== ip.user_name))
54
- throw new Forbidden(`
55
- I noticed you're trying to use multiple accounts.
56
- Only one account is allowed per IP address.
57
- If you think I'm wrong, please contact me at contact@gaetan-legac.fr and I will solve the problem.
58
- `);*/
59
- }
60
-
61
- public async botsAndMultiaccount( usernameWhitelist?: string ) {
62
- const ip = await this.bots();
63
- await this.multiaccount(ip, usernameWhitelist);
64
- return ip;
65
- }
66
-
67
- public async conflictOfInterest( username: string, request: ServerRequest ) {
68
-
69
- // NOTE: Don't base conflict of interest detection on IP
70
-
71
- // If the current ip have never been used by username
72
-
73
- /*const conflict = await sql`
74
- FROM UserLogin
75
- WHERE ip = ${request.ip} AND user = ${username}
76
- `.exists();
77
-
78
- return conflict;*/
79
-
80
- return false;
81
-
82
- }
83
-
84
- public async captcha(token?: string) {
85
-
86
- console.info(`Validation du captcha`, {
87
- secret: app.config.http.security.recaptcha.prv,
88
- response: token
89
- });
90
-
91
- if (!token)
92
- throw new Forbidden("Le captcha n'a pas été complété.");
93
-
94
- const res = await got.post('https://www.google.com/recaptcha/api/siteverify', {
95
- body: JSON.stringify({
96
- secret: app.config.http.security.recaptcha.prv,
97
- response: token,
98
- remoteip: null
99
- })
100
- }).json()
101
-
102
- console.info(`Réponse captcha`, res);
103
-
104
- const ok = res.success || false;
105
-
106
- if (!ok) throw new Forbidden("Le captcha est incorrect");
107
- }
108
-
109
- }
@@ -1,272 +0,0 @@
1
- /*----------------------------------
2
- - DEPENDANCES
3
- ----------------------------------*/
4
-
5
- // Npm
6
- import locale from 'locale'
7
- import dayjs from 'dayjs';
8
- import got from 'got';
9
-
10
- // Core
11
- import Application, { Service } from '@server/app';
12
- import type Router from '@server/services/router'
13
- import type ServerRequest from '@server/services/router/request'
14
- import type SQL from '@server/services/database';
15
- import { arrayToObj } from '@common/data/tableaux';
16
- import { Forbidden } from '@common/errors';
17
-
18
- /*----------------------------------
19
- - TYPES
20
- ----------------------------------*/
21
-
22
- type IP = {
23
- // Identity
24
- ip: string,
25
- address: string,
26
- isp: string,
27
- country: string,
28
- iphub: number,
29
- // Status
30
- banned: Date,
31
- banReason?: string,
32
- updated?: Date
33
- }
34
-
35
- export type TrackingInfos = {
36
- user: User | null,
37
- ip: IP,
38
- country: string,
39
- langue: string
40
- }
41
-
42
- type TAssoChaines = { [id: string]: string }
43
-
44
- /*----------------------------------
45
- - SERVICE
46
- ----------------------------------*/
47
-
48
- export type Config = {
49
- ga: {
50
- pub: string,
51
- prv: string,
52
- secret: string,
53
- }
54
- }
55
-
56
- export type Hooks = {
57
-
58
- }
59
-
60
- export default class TrackerService extends Service<Config, Hooks, Application> {
61
-
62
- public countries!: TAssoChaines;
63
- public langues!: TAssoChaines;
64
- public locales!: locale.Locales;
65
-
66
- public async register() {
67
-
68
- }
69
-
70
- public async start() {
71
-
72
- await this.indexData();
73
-
74
-
75
-
76
- }
77
-
78
- private async indexData() {
79
-
80
- // On n'oublie pas le tri alphabétique pour les listings
81
- /*const [countries, langues] = await this.app.sql`
82
- SELECT id, name FROM Countries ORDER BY name ASC;
83
- SELECT id, name FROM Locales ORDER BY name ASC;
84
- `().then(([listePays, listeLangues]) => [
85
- arrayToObj(listePays, { index: 'id', val: 'name' }),
86
- arrayToObj(listeLangues, { index: 'id', val: 'name' }),
87
- ]);
88
-
89
- this.countries = countries as TAssoChaines;
90
- this.langues = langues as TAssoChaines;
91
- this.locales = new locale.Locales(Object.keys(this.langues));*/
92
- }
93
-
94
- public async request( request: ServerRequest ) {
95
- return new TrackingRequestService(request, this);
96
- }
97
- }
98
-
99
- /*----------------------------------
100
- - REQUEST SERVICE
101
- ----------------------------------*/
102
- export class TrackingRequestService {
103
-
104
- // Services
105
- protected sql: SQL;
106
-
107
- // Caches
108
- private langue?: string;
109
- private ip?: IP;
110
-
111
- public constructor(
112
- private request: ServerRequest,
113
- private tracker: TrackerService,
114
- ) {
115
-
116
- this.sql = tracker.sql;
117
-
118
- }
119
-
120
- public event( event: 'pageview' ) {
121
-
122
- // Ne compte pas les events lorsque mode dev ou admin
123
- if (this.app.env.profile == 'dev' ||
124
- (this.request.user && this.request.user.roles.includes('ADMIN'))
125
- )
126
- return;
127
-
128
- console.log(`[router][request] Send GA event ${event}`);
129
- /*got.post(`https://www.google-analytics.com/mp/collect`
130
- + `?measurement_id=${app.config.tracking.ga.pub}`
131
- + `&api_secret=${app.config.tracking.ga.secret}`, {
132
-
133
- body: JSON.stringify({
134
- client_id: app.config.tracking.ga.prv,
135
- events: [{
136
- name: 'pageview',
137
- params: {},
138
- }]
139
- })
140
- })*/
141
-
142
- }
143
-
144
- public async infos(): Promise<TrackingInfos> {
145
-
146
- if (this.langue === undefined)
147
- this.langue = this.getLangue();
148
-
149
- if (this.ip === undefined)
150
- this.ip = await this.checkIP();
151
-
152
- return {
153
- user: this.request.user || null,
154
- langue: this.langue,
155
- country: this.ip.country,
156
- ip: this.ip
157
- }
158
- }
159
-
160
- public static values({ user, ip, langue, country }: TrackingInfos) {
161
- return {
162
- user: user ? user.email : null,
163
- ip: ip.address,
164
- country,
165
- langue
166
- }
167
- }
168
-
169
- private getLangue() {
170
-
171
- const locales = new locale.Locales( this.request.headers["accept-language"] )
172
- const langue = locales.best( this.tracker.locales ).language.toUpperCase();
173
-
174
- return (langue in this.tracker.langues) ? langue : 'EN';
175
-
176
- }
177
-
178
- public async checkIP(): Promise<IP> {
179
-
180
- let address: string = this.request.ip;
181
-
182
- // Détection multicompte: 1 Compte max / IP
183
- console.log('Checking IP ...', address);
184
- const now: Date = new Date;
185
-
186
- let ip = await this.sql`SELECT * FROM logs.IP WHERE address = ${address}`.first();
187
- if (!ip) {
188
-
189
- console.log(`New IP`);
190
-
191
- ip = {
192
- address,
193
- meet: now,
194
- activity: now,
195
- user_name: this.request.user?.name,
196
- }
197
-
198
- await this.retrieveScore(ip);
199
-
200
- this.sql.insert("logs.IP", ip);
201
-
202
- // Nouvelle IP
203
- } else {
204
-
205
- console.log(`Existing IP`, address, ip.banned);
206
-
207
- // Déjà banni
208
- if (ip.banned)
209
- throw new Forbidden(`Banned for the following reason: ` + ip.banReason);
210
-
211
- // Données expirées
212
- const tempsDepuisDerniereMaj = dayjs().diff(ip.dateMaj, 'day');
213
- if (tempsDepuisDerniereMaj >= 7) {
214
- console.log(`Dernière màj il y a ` + tempsDepuisDerniereMaj + ' jours');
215
- await this.retrieveScore(ip);
216
- }
217
-
218
- ip.activity = now;
219
-
220
- this.sql.update("logs.IP", ip, { address });
221
-
222
- }
223
-
224
- return ip;
225
- }
226
-
227
- private async retrieveScore(ip: IP) {
228
-
229
- console.log(ip.address, `Computing score ...`);
230
-
231
- // Retourner true pour autoriser
232
- const [iphubOk] = await Promise.all([
233
-
234
- // IPhub = le plus fiable en premier
235
- got.get('http://v2.api.iphub.info/ip/' + ip.address, {
236
- headers: {
237
- 'X-Key': app.config.http.security.iphub
238
- }
239
- }).json().then((iphub: any) => {
240
-
241
- ip.iphub = iphub.block;
242
- ip.country = iphub.countryCode;
243
- ip.isp = iphub.isp;
244
-
245
- console.log(ip.address, "IpHub:", iphub);
246
-
247
- /*
248
- block: 0 - Residential or business IP (i.e. safe IP)
249
- block: 1 - Non-residential IP (hosting provider, proxy, etc.)
250
- block: 2 - Non-residential & residential IP (warning, may flag innocent people)
251
- */
252
- return ip.iphub !== 1;
253
-
254
- })
255
-
256
- ]);
257
-
258
- // Impossible d'avoir le country = douteux
259
- // NOTE: Pour Iphub, ZZ = inconnu
260
- if (!(ip.country in this.tracker.countries)) {
261
- ip.banned = new Date;
262
- ip.banReason = "Invalid location: " + ip.country;
263
- // Considéré comme suspect par iphub etou getipintel
264
- } else if (!(iphubOk)) {
265
- ip.banned = new Date;
266
- ip.banReason = "Suspicious activity on your network";
267
- }
268
-
269
- ip.updated = new Date;
270
- }
271
-
272
- }