@proteinjs/user-server 1.1.4 → 1.2.0

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.
@@ -0,0 +1,230 @@
1
+ import { getDb, getDbAsSystem } from '@proteinjs/db';
2
+ import {
3
+ SendInviteResponse,
4
+ tables,
5
+ UserAuth,
6
+ uiRoutes,
7
+ SignupService,
8
+ InitializeSignupResponse,
9
+ UserRepo,
10
+ Invite,
11
+ UserSignup,
12
+ } from '@proteinjs/user';
13
+ import moment from 'moment';
14
+ import { lib } from 'crypto-js';
15
+ import { Logger } from '@proteinjs/util';
16
+ import {
17
+ EmailSender,
18
+ getDefaultInviteEmailConfigFactory,
19
+ getDefaultSignupConfirmationEmailConfigFactory,
20
+ } from '@proteinjs/email-server';
21
+ import sha256 from 'crypto-js/sha256';
22
+ import { Loadable, SourceRepository } from '@proteinjs/reflection';
23
+
24
+ export interface InviteConfig {
25
+ isInviteOnly: boolean;
26
+ }
27
+
28
+ export interface DefaultInviteConfigFactory extends Loadable {
29
+ getConfig(): InviteConfig;
30
+ }
31
+
32
+ export const getDefaultInviteConfigFactory = (): DefaultInviteConfigFactory => {
33
+ const defaultFactory: DefaultInviteConfigFactory = {
34
+ getConfig: (): InviteConfig => ({ isInviteOnly: false }),
35
+ };
36
+
37
+ const factory = SourceRepository.get().object<DefaultInviteConfigFactory>(
38
+ '@proteinjs/user-server/DefaultInviteConfigFactory'
39
+ );
40
+ return factory || defaultFactory;
41
+ };
42
+
43
+ export class Signup implements SignupService {
44
+ public serviceMetadata = {
45
+ auth: {
46
+ canAccess: (methodName: string, args: any[]) => {
47
+ if (methodName === 'sendInvite' || methodName === 'revokeInvite') {
48
+ UserAuth.hasRole('admin');
49
+ }
50
+
51
+ return true;
52
+ },
53
+ },
54
+ };
55
+
56
+ async createUser(user: UserSignup, token?: string): Promise<void> {
57
+ const logger = new Logger('SignupService: createUser');
58
+ const db = getDbAsSystem();
59
+
60
+ const initSignupResponse = await this.initializeSignup(token);
61
+ if (!initSignupResponse.isReady) {
62
+ throw new Error(initSignupResponse.error);
63
+ }
64
+
65
+ const invite = token ? await this.getValidInvite(token) : null;
66
+ if (token) {
67
+ await db.delete(tables.Invite, { token });
68
+ }
69
+
70
+ const email = invite ? invite.email : user.email;
71
+ if (!email) {
72
+ throw new Error('Email is required when there is no invite');
73
+ }
74
+
75
+ const defaultEmailConfigFactory = getDefaultSignupConfirmationEmailConfigFactory();
76
+ const config = defaultEmailConfigFactory.getConfig();
77
+ const emailSender = new EmailSender();
78
+
79
+ const userRecord = await db.get(tables.User, { email });
80
+ if (userRecord) {
81
+ logger.error(`User with this email already exists: ${email}`);
82
+ const { text, html } = config.getExistingUserEmailContent();
83
+ await emailSender.sendEmail({
84
+ to: email,
85
+ subject: config.existingUserSubject || config.options?.subject || 'Account already exists',
86
+ text,
87
+ html,
88
+ ...config.options,
89
+ });
90
+ return;
91
+ }
92
+
93
+ await db.insert(tables.User, {
94
+ name: user.name,
95
+ email,
96
+ password: sha256(user.password).toString(),
97
+ emailVerified: false,
98
+ roles: '',
99
+ invitedBy: invite ? invite.invitedBy : null,
100
+ });
101
+
102
+ const { text, html } = config.getNewUserEmailContent();
103
+ await emailSender.sendEmail({
104
+ to: email,
105
+ subject: config.newUserSubject || config.options?.subject || 'Welcome!',
106
+ text,
107
+ html,
108
+ ...config.options,
109
+ });
110
+ logger.info(`Created user: ${email}`);
111
+ }
112
+
113
+ async sendInvite(email: string): Promise<SendInviteResponse> {
114
+ const logger = new Logger('SignupService: sendInvite');
115
+ try {
116
+ const db = getDbAsSystem();
117
+ const userRecord = await db.get(tables.User, { email });
118
+ if (userRecord) {
119
+ return { sent: false, error: 'User already exists with that email.' };
120
+ }
121
+
122
+ const emailSender = new EmailSender();
123
+ const defaultConfigFactory = getDefaultInviteEmailConfigFactory();
124
+ if (!defaultConfigFactory) {
125
+ throw new Error(
126
+ `Unable to find a @proteinjs/email-server/DefaultInviteEmailConfigFactory implementation when sending invite.`
127
+ );
128
+ }
129
+ const config = defaultConfigFactory.getConfig();
130
+
131
+ const token = lib.WordArray.random(32).toString();
132
+ const tokenExpiresAt = moment().add(7, 'days');
133
+ let invite = await db.get(tables.Invite, { email });
134
+ if (invite) {
135
+ invite = {
136
+ ...invite,
137
+ token,
138
+ tokenExpiresAt,
139
+ };
140
+ await db.update(tables.Invite, invite);
141
+ } else {
142
+ const userId = new UserRepo().getUser().id;
143
+ invite = await db.insert(tables.Invite, { email, token, tokenExpiresAt, invitedBy: userId });
144
+ }
145
+
146
+ const { text, html } = config.getEmailContent(`${uiRoutes.auth.signup}?token=${token}`);
147
+ await emailSender.sendEmail({
148
+ to: email,
149
+ subject: config.options?.subject || `You're Invited`,
150
+ text,
151
+ html,
152
+ ...config.options,
153
+ });
154
+
155
+ return { sent: true };
156
+ } catch (error: any) {
157
+ logger.error('Error: ', error);
158
+ return {
159
+ sent: false,
160
+ error: 'Error occurred.',
161
+ };
162
+ }
163
+ }
164
+
165
+ async revokeInvite(email: string): Promise<void> {
166
+ if (!email) {
167
+ throw new Error('No email was provided.');
168
+ }
169
+
170
+ const db = getDb();
171
+ await db.delete(tables.Invite, { email });
172
+ }
173
+
174
+ /**
175
+ * Initializes signup process, validating invite configuration and token if provided.
176
+ * `DefaultInviteConfigFactory` defaults to invite optional.
177
+ */
178
+ async initializeSignup(inviteToken: string | undefined): Promise<InitializeSignupResponse> {
179
+ try {
180
+ const config = getDefaultInviteConfigFactory().getConfig();
181
+ const { isInviteOnly } = config;
182
+ const invite = inviteToken ? await this.getValidInvite(inviteToken) : undefined;
183
+
184
+ if (isInviteOnly) {
185
+ if (!inviteToken) {
186
+ return {
187
+ isReady: false,
188
+ error: 'An invite is required to sign up.',
189
+ isInviteOnly,
190
+ };
191
+ }
192
+ if (!invite) {
193
+ return {
194
+ isReady: false,
195
+ error: 'The provided invite was not found or has expired.',
196
+ isInviteOnly,
197
+ };
198
+ }
199
+ }
200
+
201
+ return {
202
+ isReady: true,
203
+ isInviteOnly,
204
+ invite: invite ? invite : undefined,
205
+ };
206
+ } catch (error: any) {
207
+ return {
208
+ isReady: false,
209
+ error: 'Initializing sign up failed.',
210
+ };
211
+ }
212
+ }
213
+
214
+ /** Returns a valid invite if it exists and is not expired, returns `null` otherwise. */
215
+ private async getValidInvite(token: string): Promise<Invite | null> {
216
+ const db = getDbAsSystem();
217
+ const invite = await db.get(tables.Invite, { token });
218
+
219
+ if (!invite) {
220
+ return null;
221
+ }
222
+
223
+ const currentTime = moment();
224
+ if (invite.tokenExpiresAt && moment(invite.tokenExpiresAt).isBefore(currentTime)) {
225
+ return null;
226
+ }
227
+
228
+ return invite;
229
+ }
230
+ }
@@ -1,3 +0,0 @@
1
- import { Route } from '@proteinjs/server-api';
2
- export declare const createUser: Route;
3
- //# sourceMappingURL=createUser.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"createUser.d.ts","sourceRoot":"","sources":["../../../src/routes/createUser.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAK9C,eAAO,MAAM,UAAU,EAAE,KAyBxB,CAAC"}
@@ -1,83 +0,0 @@
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 __generator = (this && this.__generator) || function (thisArg, body) {
12
- var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
- return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
- function verb(n) { return function (v) { return step([n, v]); }; }
15
- function step(op) {
16
- if (f) throw new TypeError("Generator is already executing.");
17
- while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
- if (y = 0, t) op = [op[0] & 2, t.value];
20
- switch (op[0]) {
21
- case 0: case 1: t = op; break;
22
- case 4: _.label++; return { value: op[1], done: false };
23
- case 5: _.label++; y = op[1]; op = [0]; continue;
24
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
- default:
26
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
- if (t[2]) _.ops.pop();
31
- _.trys.pop(); continue;
32
- }
33
- op = body.call(thisArg, _);
34
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
- }
37
- };
38
- var __importDefault = (this && this.__importDefault) || function (mod) {
39
- return (mod && mod.__esModule) ? mod : { "default": mod };
40
- };
41
- Object.defineProperty(exports, "__esModule", { value: true });
42
- exports.createUser = void 0;
43
- var sha256_1 = __importDefault(require("crypto-js/sha256"));
44
- var db_1 = require("@proteinjs/db");
45
- var user_1 = require("@proteinjs/user");
46
- var util_1 = require("@proteinjs/util");
47
- exports.createUser = {
48
- path: user_1.routes.createUser.path,
49
- method: user_1.routes.createUser.method,
50
- onRequest: function (request, response) { return __awaiter(void 0, void 0, void 0, function () {
51
- var logger, user, db, userRecord, error;
52
- return __generator(this, function (_a) {
53
- switch (_a.label) {
54
- case 0:
55
- logger = new util_1.Logger('createUser');
56
- user = request.body;
57
- db = (0, db_1.getDbAsSystem)();
58
- return [4 /*yield*/, db.get(user_1.tables.User, { email: user.email })];
59
- case 1:
60
- userRecord = _a.sent();
61
- if (userRecord) {
62
- error = "User with this email already exists";
63
- console.error("".concat(error, ": ").concat(user.email));
64
- response.send({ error: error });
65
- return [2 /*return*/];
66
- }
67
- return [4 /*yield*/, db.insert(user_1.tables.User, {
68
- name: user.name,
69
- email: user.email,
70
- password: (0, sha256_1.default)(user.password).toString(),
71
- emailVerified: false,
72
- roles: '',
73
- })];
74
- case 2:
75
- _a.sent();
76
- logger.info("Created user: ".concat(user.email));
77
- response.send({});
78
- return [2 /*return*/];
79
- }
80
- });
81
- }); },
82
- };
83
- //# sourceMappingURL=createUser.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"createUser.js","sourceRoot":"","sources":["../../../src/routes/createUser.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,4DAAsC;AAEtC,oCAA8C;AAC9C,wCAAuD;AACvD,wCAAyC;AAE5B,QAAA,UAAU,GAAU;IAC/B,IAAI,EAAE,aAAM,CAAC,UAAU,CAAC,IAAI;IAC5B,MAAM,EAAE,aAAM,CAAC,UAAU,CAAC,MAAM;IAChC,SAAS,EAAE,UAAO,OAAO,EAAE,QAAQ;;;;;oBAC3B,MAAM,GAAG,IAAI,aAAM,CAAC,YAAY,CAAC,CAAC;oBAClC,IAAI,GAAG,OAAO,CAAC,IAAY,CAAC;oBAC5B,EAAE,GAAG,IAAA,kBAAa,GAAE,CAAC;oBACR,qBAAM,EAAE,CAAC,GAAG,CAAC,aAAM,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,EAAA;;oBAA7D,UAAU,GAAG,SAAgD;oBACnE,IAAI,UAAU,EAAE;wBACR,KAAK,GAAG,qCAAqC,CAAC;wBACpD,OAAO,CAAC,KAAK,CAAC,UAAG,KAAK,eAAK,IAAI,CAAC,KAAK,CAAE,CAAC,CAAC;wBACzC,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,OAAA,EAAE,CAAC,CAAC;wBACzB,sBAAO;qBACR;oBAED,qBAAM,EAAE,CAAC,MAAM,CAAC,aAAM,CAAC,IAAI,EAAE;4BAC3B,IAAI,EAAE,IAAI,CAAC,IAAI;4BACf,KAAK,EAAE,IAAI,CAAC,KAAK;4BACjB,QAAQ,EAAE,IAAA,gBAAM,EAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE;4BAC1C,aAAa,EAAE,KAAK;4BACpB,KAAK,EAAE,EAAE;yBACV,CAAC,EAAA;;oBANF,SAME,CAAC;oBACH,MAAM,CAAC,IAAI,CAAC,wBAAiB,IAAI,CAAC,KAAK,CAAE,CAAC,CAAC;oBAC3C,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;;;;SACnB;CACF,CAAC"}
@@ -1,32 +0,0 @@
1
- import sha256 from 'crypto-js/sha256';
2
- import { Route } from '@proteinjs/server-api';
3
- import { getDbAsSystem } from '@proteinjs/db';
4
- import { User, tables, routes } from '@proteinjs/user';
5
- import { Logger } from '@proteinjs/util';
6
-
7
- export const createUser: Route = {
8
- path: routes.createUser.path,
9
- method: routes.createUser.method,
10
- onRequest: async (request, response): Promise<void> => {
11
- const logger = new Logger('createUser');
12
- const user = request.body as User;
13
- const db = getDbAsSystem();
14
- const userRecord = await db.get(tables.User, { email: user.email });
15
- if (userRecord) {
16
- const error = `User with this email already exists`;
17
- console.error(`${error}: ${user.email}`);
18
- response.send({ error });
19
- return;
20
- }
21
-
22
- await db.insert(tables.User, {
23
- name: user.name,
24
- email: user.email,
25
- password: sha256(user.password).toString(),
26
- emailVerified: false,
27
- roles: '',
28
- });
29
- logger.info(`Created user: ${user.email}`);
30
- response.send({});
31
- },
32
- };