@skroz/profile-api 1.0.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/LICENCE.md +21 -0
- package/dist/adapters/TypeOrmProfileAdapter.d.ts +13 -0
- package/dist/adapters/TypeOrmProfileAdapter.js +54 -0
- package/dist/dto/ConfirmEmailInput.d.ts +6 -0
- package/dist/dto/ConfirmEmailInput.js +27 -0
- package/dist/dto/ForgotPasswordInput.d.ts +9 -0
- package/dist/dto/ForgotPasswordInput.js +31 -0
- package/dist/dto/LoginInput.d.ts +12 -0
- package/dist/dto/LoginInput.js +31 -0
- package/dist/dto/PasswordInput.d.ts +9 -0
- package/dist/dto/PasswordInput.js +31 -0
- package/dist/dto/RecoverPasswordInput.d.ts +12 -0
- package/dist/dto/RecoverPasswordInput.js +28 -0
- package/dist/dto/RegisterInput.d.ts +16 -0
- package/dist/dto/RegisterInput.js +40 -0
- package/dist/dto/SendTokenPayload.d.ts +4 -0
- package/dist/dto/SendTokenPayload.js +27 -0
- package/dist/dto/StatusPayload.d.ts +3 -0
- package/dist/dto/StatusPayload.js +23 -0
- package/dist/dto/UpdateEmailInput.d.ts +9 -0
- package/dist/dto/UpdateEmailInput.js +31 -0
- package/dist/dto/UpdatePasswordInput.d.ts +12 -0
- package/dist/dto/UpdatePasswordInput.js +28 -0
- package/dist/dto/UpdateProfileInput.d.ts +9 -0
- package/dist/dto/UpdateProfileInput.js +31 -0
- package/dist/dto/index.d.ts +11 -0
- package/dist/dto/index.js +27 -0
- package/dist/entities/TypeOrmBaseUser.d.ts +25 -0
- package/dist/entities/TypeOrmBaseUser.js +117 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +24 -0
- package/dist/resolvers/AuthResolver.d.ts +38 -0
- package/dist/resolvers/AuthResolver.js +219 -0
- package/dist/resolvers/ProfileResolver.d.ts +24 -0
- package/dist/resolvers/ProfileResolver.js +145 -0
- package/dist/services/ProfileAuthService.d.ts +24 -0
- package/dist/services/ProfileAuthService.js +94 -0
- package/dist/services/ProfileEmailService.d.ts +15 -0
- package/dist/services/ProfileEmailService.js +105 -0
- package/dist/types/index.d.ts +98 -0
- package/dist/types/index.js +10 -0
- package/dist/validators/isTrue.d.ts +3 -0
- package/dist/validators/isTrue.js +7 -0
- package/package.json +45 -0
- package/src/adapters/TypeOrmProfileAdapter.ts +40 -0
- package/src/dto/ConfirmEmailInput.ts +12 -0
- package/src/dto/ForgotPasswordInput.ts +17 -0
- package/src/dto/LoginInput.ts +20 -0
- package/src/dto/PasswordInput.ts +17 -0
- package/src/dto/RecoverPasswordInput.ts +20 -0
- package/src/dto/RegisterInput.ts +29 -0
- package/src/dto/SendTokenPayload.ts +10 -0
- package/src/dto/StatusPayload.ts +7 -0
- package/src/dto/UpdateEmailInput.ts +17 -0
- package/src/dto/UpdatePasswordInput.ts +20 -0
- package/src/dto/UpdateProfileInput.ts +17 -0
- package/src/dto/index.ts +11 -0
- package/src/entities/TypeOrmBaseUser.ts +87 -0
- package/src/index.ts +8 -0
- package/src/resolvers/AuthResolver.ts +195 -0
- package/src/resolvers/ProfileResolver.ts +107 -0
- package/src/services/ProfileAuthService.ts +122 -0
- package/src/services/ProfileEmailService.ts +158 -0
- package/src/types/index.ts +102 -0
- package/src/validators/isTrue.ts +8 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.ProfileAuthService = exports.LAST_SENT_AT_REDIS_POSTFIX = exports.TOKEN_REDIS_POSTFIX = exports.RECOVERY_REDIS_PREFIX = exports.CONFIRMATION_REDIS_PREFIX = void 0;
|
|
16
|
+
const argon2_1 = __importDefault(require("argon2"));
|
|
17
|
+
exports.CONFIRMATION_REDIS_PREFIX = 'conf';
|
|
18
|
+
exports.RECOVERY_REDIS_PREFIX = 'rec';
|
|
19
|
+
exports.TOKEN_REDIS_POSTFIX = 'token';
|
|
20
|
+
exports.LAST_SENT_AT_REDIS_POSTFIX = 'lastSentAt';
|
|
21
|
+
class ProfileAuthService {
|
|
22
|
+
constructor(db, email, redis, config) {
|
|
23
|
+
this.db = db;
|
|
24
|
+
this.email = email;
|
|
25
|
+
this.redis = redis;
|
|
26
|
+
this.config = config;
|
|
27
|
+
}
|
|
28
|
+
hashPassword(password) {
|
|
29
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
return argon2_1.default.hash(password, { type: argon2_1.default.argon2id });
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
verifyPassword(hash, password) {
|
|
34
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
35
|
+
return argon2_1.default.verify(hash, password);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
getLimitMs() {
|
|
39
|
+
return this.config.resendEmailLimitSeconds * 1000;
|
|
40
|
+
}
|
|
41
|
+
setTokenToRedis(prefix, user, token, ttlMinutes) {
|
|
42
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
43
|
+
const exSeconds = ttlMinutes * 60;
|
|
44
|
+
// token -> userId
|
|
45
|
+
yield this.redis.setex(`${prefix}:${token}`, exSeconds, user.id.toString());
|
|
46
|
+
// userId -> token
|
|
47
|
+
yield this.redis.setex(`${prefix}:${user.id}:${exports.TOKEN_REDIS_POSTFIX}`, exSeconds, token);
|
|
48
|
+
// lastSentAt
|
|
49
|
+
yield this.redis.psetex(`${prefix}:${user.id}:${exports.LAST_SENT_AT_REDIS_POSTFIX}`, this.getLimitMs(), Date.now().toString());
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
sendLink(user, type) {
|
|
53
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
54
|
+
const prefix = type === 'confirmation'
|
|
55
|
+
? exports.CONFIRMATION_REDIS_PREFIX
|
|
56
|
+
: exports.RECOVERY_REDIS_PREFIX;
|
|
57
|
+
const ttlMinutes = type === 'confirmation'
|
|
58
|
+
? this.config.confirmationTokenLifetimeMinutes
|
|
59
|
+
: this.config.recoveryTokenLifetimeMinutes;
|
|
60
|
+
// Check limit
|
|
61
|
+
const lastSentAtStr = yield this.redis.get(`${prefix}:${user.id}:${exports.LAST_SENT_AT_REDIS_POSTFIX}`);
|
|
62
|
+
const lastSentAt = Number(lastSentAtStr) || 0;
|
|
63
|
+
const expiresAt = lastSentAt + this.getLimitMs();
|
|
64
|
+
const expiresIn = expiresAt - Date.now();
|
|
65
|
+
if (expiresIn > 0)
|
|
66
|
+
return { ok: false, limitExpiresAt: expiresAt };
|
|
67
|
+
// Generate token (6 digits as in original code)
|
|
68
|
+
let token = '';
|
|
69
|
+
for (let i = 0; i < 6; i++) {
|
|
70
|
+
token += Math.floor(Math.random() * 10);
|
|
71
|
+
}
|
|
72
|
+
yield this.setTokenToRedis(prefix, user, token, ttlMinutes);
|
|
73
|
+
const ok = yield this.email.sendToken(user, type, token);
|
|
74
|
+
return { ok, limitExpiresAt: Date.now() + this.getLimitMs() };
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
getUserByToken(prefix, token) {
|
|
78
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
79
|
+
const userIdStr = yield this.redis.get(`${prefix}:${token}`);
|
|
80
|
+
if (!userIdStr)
|
|
81
|
+
return null;
|
|
82
|
+
const userId = Number(userIdStr);
|
|
83
|
+
if (isNaN(userId))
|
|
84
|
+
return null;
|
|
85
|
+
return this.db.findUserById(userId);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
removeTokenFromRedis(prefix, user, token) {
|
|
89
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
90
|
+
yield this.redis.del(`${prefix}:${token}`, `${prefix}:${user.id}:${exports.TOKEN_REDIS_POSTFIX}`, `${prefix}:${user.id}:${exports.LAST_SENT_AT_REDIS_POSTFIX}`);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
exports.ProfileAuthService = ProfileAuthService;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AuthUser, EmailConfig, ProfileLocales } from '../types';
|
|
2
|
+
export type EmailType = 'auth' | 'activity' | 'info' | 'admin' | 'other';
|
|
3
|
+
export declare class ProfileEmailService {
|
|
4
|
+
private ses;
|
|
5
|
+
private config;
|
|
6
|
+
private locales;
|
|
7
|
+
constructor(config: EmailConfig, locales: ProfileLocales);
|
|
8
|
+
private getWebsiteUrl;
|
|
9
|
+
private render;
|
|
10
|
+
private getFrom;
|
|
11
|
+
send(to: string, subject: string, template: string, vars: any, type?: EmailType): Promise<boolean>;
|
|
12
|
+
sendToken(user: AuthUser, type: 'confirmation' | 'recovery', token: string): Promise<boolean>;
|
|
13
|
+
sendTemporaryPassword(user: AuthUser, tempPassword: string): Promise<boolean>;
|
|
14
|
+
sendGenericEmail(user: AuthUser, subject: string, text: string, type?: EmailType, vars?: any): Promise<boolean>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.ProfileEmailService = void 0;
|
|
16
|
+
const aws_ses_1 = __importDefault(require("@os-team/aws-ses"));
|
|
17
|
+
const pug_1 = __importDefault(require("pug"));
|
|
18
|
+
const path_1 = __importDefault(require("path"));
|
|
19
|
+
const types_1 = require("../types");
|
|
20
|
+
class ProfileEmailService {
|
|
21
|
+
constructor(config, locales) {
|
|
22
|
+
this.ses = new aws_ses_1.default({ region: 'eu-west-1' });
|
|
23
|
+
this.config = config;
|
|
24
|
+
this.locales = locales;
|
|
25
|
+
}
|
|
26
|
+
getWebsiteUrl() {
|
|
27
|
+
return this.config.websiteUrl || `https://${this.config.domain}`;
|
|
28
|
+
}
|
|
29
|
+
render(templateName, vars) {
|
|
30
|
+
const templatePath = path_1.default.resolve(this.config.templateDir, `${templateName}.pug`);
|
|
31
|
+
return pug_1.default.renderFile(templatePath, Object.assign({ domain: this.config.domain, websiteUrl: this.getWebsiteUrl(), primaryBrandColor: this.config.primaryBrandColor, logoUrl: this.config.logoUrl || '' }, vars));
|
|
32
|
+
}
|
|
33
|
+
getFrom(type) {
|
|
34
|
+
const { domain } = this.config;
|
|
35
|
+
const username = this.config.fromEmailUsername;
|
|
36
|
+
switch (type) {
|
|
37
|
+
case 'auth':
|
|
38
|
+
return `${domain} <${username}@auth.${domain}>`;
|
|
39
|
+
case 'activity':
|
|
40
|
+
return `${domain} <${username}@a.${domain}>`;
|
|
41
|
+
case 'info':
|
|
42
|
+
return `${domain} <${username}@i.${domain}>`;
|
|
43
|
+
case 'admin':
|
|
44
|
+
return `${domain} <${username}@admin.${domain}>`;
|
|
45
|
+
default:
|
|
46
|
+
return `${domain} <${username}@o.${domain}>`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
send(to_1, subject_1, template_1, vars_1) {
|
|
50
|
+
return __awaiter(this, arguments, void 0, function* (to, subject, template, vars, type = 'other') {
|
|
51
|
+
const html = this.render(template, vars);
|
|
52
|
+
const from = this.getFrom(type);
|
|
53
|
+
return this.ses.send({
|
|
54
|
+
Destination: { ToAddresses: [to] },
|
|
55
|
+
Message: {
|
|
56
|
+
Subject: { Data: subject, Charset: 'UTF-8' },
|
|
57
|
+
Body: { Html: { Data: html, Charset: 'UTF-8' } },
|
|
58
|
+
},
|
|
59
|
+
Source: from,
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
sendToken(user, type, token) {
|
|
64
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
65
|
+
if (!user.email)
|
|
66
|
+
return false;
|
|
67
|
+
const isConfirm = type === 'confirmation';
|
|
68
|
+
const emailLocales = isConfirm
|
|
69
|
+
? this.locales.email.confirmEmail
|
|
70
|
+
: this.locales.email.forgotPassword;
|
|
71
|
+
const template = isConfirm
|
|
72
|
+
? types_1.ProfileEmailTemplate.CONFIRM_EMAIL
|
|
73
|
+
: types_1.ProfileEmailTemplate.FORGOT_PASSWORD;
|
|
74
|
+
return this.send(user.email, emailLocales.subject, template, {
|
|
75
|
+
header: emailLocales.header,
|
|
76
|
+
text: emailLocales.text,
|
|
77
|
+
linkTitle: emailLocales.linkTitle,
|
|
78
|
+
linkHref: `${this.getWebsiteUrl()}/${type}?token=${token}&email=${user.email}`,
|
|
79
|
+
confirmationToken: token,
|
|
80
|
+
}, 'auth');
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
sendTemporaryPassword(user, tempPassword) {
|
|
84
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
85
|
+
if (!user.email)
|
|
86
|
+
return false;
|
|
87
|
+
const emailLocales = this.locales.email.tempPassword;
|
|
88
|
+
return this.send(user.email, emailLocales.subject, types_1.ProfileEmailTemplate.TEMP_PASSWORD, {
|
|
89
|
+
header: emailLocales.header.replace('{{tempPassword}}', tempPassword),
|
|
90
|
+
text: emailLocales.text,
|
|
91
|
+
linkTitle: emailLocales.linkTitle,
|
|
92
|
+
linkHref: this.getWebsiteUrl(),
|
|
93
|
+
}, 'auth');
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/* Generic method for any custom project emails */
|
|
97
|
+
sendGenericEmail(user_1, subject_1, text_1) {
|
|
98
|
+
return __awaiter(this, arguments, void 0, function* (user, subject, text, type = 'info', vars = {}) {
|
|
99
|
+
if (!user.email)
|
|
100
|
+
return false;
|
|
101
|
+
return this.send(user.email, subject, types_1.ProfileEmailTemplate.GENERIC_NOTIFICATION, Object.assign({ header: subject, text, linkTitle: 'Go to Website', linkHref: this.getWebsiteUrl() }, vars), type);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
exports.ProfileEmailService = ProfileEmailService;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
export interface AuthUser {
|
|
2
|
+
id: number;
|
|
3
|
+
email: string | null;
|
|
4
|
+
name?: string | null;
|
|
5
|
+
avatar?: string | null;
|
|
6
|
+
password?: string;
|
|
7
|
+
isEmailConfirmed: boolean;
|
|
8
|
+
isBanned: boolean;
|
|
9
|
+
isTempPassword: boolean;
|
|
10
|
+
urlSlug?: string | null;
|
|
11
|
+
telegramId?: string | null;
|
|
12
|
+
isEmailNotificationEnabled: boolean;
|
|
13
|
+
isTelegramNotificationEnabled: boolean;
|
|
14
|
+
lastSeenAt: Date;
|
|
15
|
+
save(): Promise<this>;
|
|
16
|
+
}
|
|
17
|
+
export interface ProfileDbAdapter {
|
|
18
|
+
findUserByEmail(email: string): Promise<AuthUser | null>;
|
|
19
|
+
findUserById(id: number): Promise<AuthUser | null>;
|
|
20
|
+
findUserByTelegramId(telegramId: string): Promise<AuthUser | null>;
|
|
21
|
+
createUser(data: {
|
|
22
|
+
email: string;
|
|
23
|
+
passwordHash: string;
|
|
24
|
+
}): Promise<AuthUser>;
|
|
25
|
+
isEmailTaken(email: string, excludeUserId?: number): Promise<boolean>;
|
|
26
|
+
}
|
|
27
|
+
export interface ProfileAuthConfig {
|
|
28
|
+
resendEmailLimitSeconds: number;
|
|
29
|
+
confirmationTokenLifetimeMinutes: number;
|
|
30
|
+
recoveryTokenLifetimeMinutes: number;
|
|
31
|
+
}
|
|
32
|
+
export interface EmailConfig extends ProfileAuthConfig {
|
|
33
|
+
domain: string;
|
|
34
|
+
websiteUrl?: string;
|
|
35
|
+
primaryBrandColor: string;
|
|
36
|
+
logoUrl?: string;
|
|
37
|
+
fromEmailUsername?: string;
|
|
38
|
+
templateDir: string;
|
|
39
|
+
isOnlineSeconds: number;
|
|
40
|
+
isOnlineRecentlySeconds: number;
|
|
41
|
+
}
|
|
42
|
+
export declare enum ProfileEmailTemplate {
|
|
43
|
+
CONFIRM_EMAIL = "confirmEmail",
|
|
44
|
+
FORGOT_PASSWORD = "forgotPassword",
|
|
45
|
+
TEMP_PASSWORD = "tempPassword",
|
|
46
|
+
GENERIC_NOTIFICATION = "notification"
|
|
47
|
+
}
|
|
48
|
+
export interface ProfileLocales {
|
|
49
|
+
auth: {
|
|
50
|
+
emailConfirmed: string;
|
|
51
|
+
emailExists: string;
|
|
52
|
+
};
|
|
53
|
+
login: {
|
|
54
|
+
wrongPassword: string;
|
|
55
|
+
};
|
|
56
|
+
user: {
|
|
57
|
+
notFound: string;
|
|
58
|
+
banned: string;
|
|
59
|
+
};
|
|
60
|
+
error: {
|
|
61
|
+
wrongCode: string;
|
|
62
|
+
};
|
|
63
|
+
forgot: {
|
|
64
|
+
errors: {
|
|
65
|
+
notRegistered: string;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
updatePassword: {
|
|
69
|
+
noPassword: string;
|
|
70
|
+
wrongOldPassword: string;
|
|
71
|
+
};
|
|
72
|
+
email: {
|
|
73
|
+
confirmEmail: {
|
|
74
|
+
subject: string;
|
|
75
|
+
header: string;
|
|
76
|
+
text: string;
|
|
77
|
+
linkTitle: string;
|
|
78
|
+
};
|
|
79
|
+
forgotPassword: {
|
|
80
|
+
subject: string;
|
|
81
|
+
header: string;
|
|
82
|
+
text: string;
|
|
83
|
+
linkTitle: string;
|
|
84
|
+
};
|
|
85
|
+
tempPassword: {
|
|
86
|
+
subject: string;
|
|
87
|
+
header: string;
|
|
88
|
+
text: string;
|
|
89
|
+
linkTitle: string;
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export interface ProfileContext<TUser = AuthUser> {
|
|
94
|
+
user?: TUser;
|
|
95
|
+
req: any;
|
|
96
|
+
res: any;
|
|
97
|
+
t: (key: string, options?: any) => string;
|
|
98
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProfileEmailTemplate = void 0;
|
|
4
|
+
var ProfileEmailTemplate;
|
|
5
|
+
(function (ProfileEmailTemplate) {
|
|
6
|
+
ProfileEmailTemplate["CONFIRM_EMAIL"] = "confirmEmail";
|
|
7
|
+
ProfileEmailTemplate["FORGOT_PASSWORD"] = "forgotPassword";
|
|
8
|
+
ProfileEmailTemplate["TEMP_PASSWORD"] = "tempPassword";
|
|
9
|
+
ProfileEmailTemplate["GENERIC_NOTIFICATION"] = "notification";
|
|
10
|
+
})(ProfileEmailTemplate || (exports.ProfileEmailTemplate = ProfileEmailTemplate = {}));
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@skroz/profile-api",
|
|
3
|
+
"version": "1.0.5",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"repository": "git@gitlab.com:skroz/libs/utils.git",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"src",
|
|
11
|
+
"!**/*.test.ts"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"clean": "rimraf dist",
|
|
15
|
+
"build": "yarn clean && tsc --outDir dist",
|
|
16
|
+
"ncu": "ncu -u"
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@os-team/aws-ses": "1.0.27",
|
|
23
|
+
"@os-team/graphql-transformers": "1.0.15",
|
|
24
|
+
"@os-team/graphql-utils": "1.1.61",
|
|
25
|
+
"@os-team/graphql-validators": "1.0.26",
|
|
26
|
+
"@os-team/session": "1.0.32",
|
|
27
|
+
"argon2": "0.30.2",
|
|
28
|
+
"class-validator": "0.13.2",
|
|
29
|
+
"nanoid": "3.3.4",
|
|
30
|
+
"pug": "3.0.2",
|
|
31
|
+
"type-graphql": "2.0.0-beta.1",
|
|
32
|
+
"typeorm": "0.2.45"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "18.0.6",
|
|
36
|
+
"@types/pug": "2.0.6",
|
|
37
|
+
"ioredis": "5.2.1",
|
|
38
|
+
"rimraf": "3.0.2",
|
|
39
|
+
"typescript": "^5.0.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"reflect-metadata": "0.1.13"
|
|
43
|
+
},
|
|
44
|
+
"gitHead": "fbc3bff05af003470fe79b7ea7d11bc29caa9ae3"
|
|
45
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Repository, getConnection, Not } from 'typeorm';
|
|
2
|
+
import { AuthUser, ProfileDbAdapter } from '../types';
|
|
3
|
+
|
|
4
|
+
export class TypeOrmProfileAdapter implements ProfileDbAdapter {
|
|
5
|
+
private repository: Repository<any>;
|
|
6
|
+
|
|
7
|
+
constructor(userEntity: any) {
|
|
8
|
+
this.repository = getConnection().getRepository(userEntity);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async findUserByEmail(email: string): Promise<AuthUser | null> {
|
|
12
|
+
return this.repository.findOne({ where: { email: email.toLowerCase() } }) as Promise<AuthUser | null>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async findUserById(id: number): Promise<AuthUser | null> {
|
|
16
|
+
return this.repository.findOne(id) as Promise<AuthUser | null>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async findUserByTelegramId(telegramId: string): Promise<AuthUser | null> {
|
|
20
|
+
return this.repository.findOne({ where: { telegramId } }) as Promise<AuthUser | null>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async createUser(data: { email: string; passwordHash: string }): Promise<AuthUser> {
|
|
24
|
+
const user = this.repository.create({
|
|
25
|
+
email: data.email.toLowerCase(),
|
|
26
|
+
password: data.passwordHash,
|
|
27
|
+
isEmailConfirmed: false,
|
|
28
|
+
});
|
|
29
|
+
return (await this.repository.save(user)) as AuthUser;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async isEmailTaken(email: string, excludeUserId?: number): Promise<boolean> {
|
|
33
|
+
const where: any = { email: email.toLowerCase() };
|
|
34
|
+
if (excludeUserId) {
|
|
35
|
+
where.id = Not(excludeUserId);
|
|
36
|
+
}
|
|
37
|
+
const count = await this.repository.count({ where });
|
|
38
|
+
return count > 0;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Field, InputType } from 'type-graphql';
|
|
2
|
+
import { isNotEmpty } from '@os-team/graphql-validators';
|
|
3
|
+
|
|
4
|
+
@InputType()
|
|
5
|
+
export class ConfirmEmailInput {
|
|
6
|
+
@Field()
|
|
7
|
+
token!: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const confirmEmailValidators = {
|
|
11
|
+
token: [isNotEmpty],
|
|
12
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Field, InputType } from 'type-graphql';
|
|
2
|
+
import { isEmail } from '@os-team/graphql-validators';
|
|
3
|
+
import { toLowerCase, trim } from '@os-team/graphql-transformers';
|
|
4
|
+
|
|
5
|
+
@InputType()
|
|
6
|
+
export class ForgotPasswordInput {
|
|
7
|
+
@Field()
|
|
8
|
+
email!: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const forgotPasswordValidators = {
|
|
12
|
+
email: [isEmail],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const forgotPasswordTransformers = {
|
|
16
|
+
email: [trim, toLowerCase],
|
|
17
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Field, InputType } from 'type-graphql';
|
|
2
|
+
import { isEmail, isNotEmpty } from '@os-team/graphql-validators';
|
|
3
|
+
import { toLowerCase, trim } from '@os-team/graphql-transformers';
|
|
4
|
+
import { PasswordInput, passwordTransformers } from './PasswordInput';
|
|
5
|
+
|
|
6
|
+
@InputType()
|
|
7
|
+
export class LoginInput extends PasswordInput {
|
|
8
|
+
@Field()
|
|
9
|
+
email!: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const loginValidators = {
|
|
13
|
+
email: [isEmail],
|
|
14
|
+
password: [isNotEmpty],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const loginTransformers = {
|
|
18
|
+
email: [trim, toLowerCase],
|
|
19
|
+
...passwordTransformers,
|
|
20
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Field, InputType } from 'type-graphql';
|
|
2
|
+
import { maxLength, minLength } from '@os-team/graphql-validators';
|
|
3
|
+
import { trim } from '@os-team/graphql-transformers';
|
|
4
|
+
|
|
5
|
+
@InputType({ isAbstract: true })
|
|
6
|
+
export class PasswordInput {
|
|
7
|
+
@Field()
|
|
8
|
+
password!: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const passwordValidators = {
|
|
12
|
+
password: [minLength(6), maxLength(64)],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const passwordTransformers = {
|
|
16
|
+
password: [trim],
|
|
17
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Field, InputType } from 'type-graphql';
|
|
2
|
+
import { isNotEmpty } from '@os-team/graphql-validators';
|
|
3
|
+
import { trim } from '@os-team/graphql-transformers';
|
|
4
|
+
import { PasswordInput, passwordTransformers, passwordValidators } from './PasswordInput';
|
|
5
|
+
|
|
6
|
+
@InputType()
|
|
7
|
+
export class RecoverPasswordInput extends PasswordInput {
|
|
8
|
+
@Field()
|
|
9
|
+
token!: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const recoverPasswordTransformers = {
|
|
13
|
+
...passwordTransformers,
|
|
14
|
+
token: [trim],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const recoverPasswordValidators = {
|
|
18
|
+
...passwordValidators,
|
|
19
|
+
token: [isNotEmpty],
|
|
20
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Field, InputType } from 'type-graphql';
|
|
2
|
+
import { isEmail } from '@os-team/graphql-validators';
|
|
3
|
+
import { toLowerCase, trim } from '@os-team/graphql-transformers';
|
|
4
|
+
import { PasswordInput, passwordTransformers, passwordValidators } from './PasswordInput';
|
|
5
|
+
import isTrue from '../validators/isTrue';
|
|
6
|
+
|
|
7
|
+
@InputType()
|
|
8
|
+
export class RegisterInput extends PasswordInput {
|
|
9
|
+
@Field()
|
|
10
|
+
email!: string;
|
|
11
|
+
|
|
12
|
+
@Field(() => Boolean, { nullable: true })
|
|
13
|
+
isUserAgreementAgree?: boolean;
|
|
14
|
+
|
|
15
|
+
@Field(() => Boolean, { nullable: true })
|
|
16
|
+
isPrivacyPolicyAgree?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const registerValidators = {
|
|
20
|
+
email: [isEmail],
|
|
21
|
+
isUserAgreementAgree: [isTrue],
|
|
22
|
+
isPrivacyPolicyAgree: [isTrue],
|
|
23
|
+
...passwordValidators,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const registerTransformers = {
|
|
27
|
+
email: [trim, toLowerCase],
|
|
28
|
+
...passwordTransformers,
|
|
29
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Field, InputType } from 'type-graphql';
|
|
2
|
+
import { isEmail, isNotEmpty } from '@os-team/graphql-validators';
|
|
3
|
+
import { toLowerCase, trim } from '@os-team/graphql-transformers';
|
|
4
|
+
|
|
5
|
+
@InputType()
|
|
6
|
+
export class UpdateEmailInput {
|
|
7
|
+
@Field()
|
|
8
|
+
email!: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const updateEmailTransformers = {
|
|
12
|
+
email: [trim, toLowerCase],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const updateEmailValidators = {
|
|
16
|
+
email: [isNotEmpty, isEmail],
|
|
17
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Field, InputType } from 'type-graphql';
|
|
2
|
+
import { isNotEmpty } from '@os-team/graphql-validators';
|
|
3
|
+
import { trim } from '@os-team/graphql-transformers';
|
|
4
|
+
import { PasswordInput, passwordTransformers, passwordValidators } from './PasswordInput';
|
|
5
|
+
|
|
6
|
+
@InputType()
|
|
7
|
+
export class UpdatePasswordInput extends PasswordInput {
|
|
8
|
+
@Field()
|
|
9
|
+
oldPassword!: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const updatePasswordTransformers = {
|
|
13
|
+
...passwordTransformers,
|
|
14
|
+
oldPassword: [trim],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const updatePasswordValidators = {
|
|
18
|
+
...passwordValidators,
|
|
19
|
+
oldPassword: [isNotEmpty],
|
|
20
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Field, InputType } from 'type-graphql';
|
|
2
|
+
import { isNotEmpty } from '@os-team/graphql-validators';
|
|
3
|
+
import { trim } from '@os-team/graphql-transformers';
|
|
4
|
+
|
|
5
|
+
@InputType()
|
|
6
|
+
export class UpdateProfileInput {
|
|
7
|
+
@Field()
|
|
8
|
+
name!: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const updateProfileTransformers = {
|
|
12
|
+
name: [trim],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const updateProfileValidators = {
|
|
16
|
+
name: [isNotEmpty],
|
|
17
|
+
};
|
package/src/dto/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from './PasswordInput';
|
|
2
|
+
export * from './RegisterInput';
|
|
3
|
+
export * from './LoginInput';
|
|
4
|
+
export * from './ForgotPasswordInput';
|
|
5
|
+
export * from './ConfirmEmailInput';
|
|
6
|
+
export * from './StatusPayload';
|
|
7
|
+
export * from './SendTokenPayload';
|
|
8
|
+
export * from './UpdatePasswordInput';
|
|
9
|
+
export * from './UpdateEmailInput';
|
|
10
|
+
export * from './UpdateProfileInput';
|
|
11
|
+
export * from './RecoverPasswordInput';
|