skapi-js 0.2.0 → 0.2.2
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/dist/skapi.js +1 -1
- package/dist/skapi.js.map +1 -1
- package/dist/skapi.module.js +1 -1
- package/dist/skapi.module.js.map +1 -1
- package/js/Main.d.ts +3 -0
- package/js/Main.js +3 -0
- package/js/Types.d.ts +194 -0
- package/js/Types.js +1 -0
- package/js/main/error.d.ts +8 -0
- package/js/main/error.js +38 -0
- package/js/main/skapi.d.ts +79 -0
- package/js/main/skapi.js +285 -0
- package/js/methods/database.d.ts +83 -0
- package/js/methods/database.js +915 -0
- package/js/methods/request.d.ts +36 -0
- package/js/methods/request.js +656 -0
- package/js/methods/subscription.d.ts +46 -0
- package/js/methods/subscription.js +240 -0
- package/js/methods/user.d.ts +67 -0
- package/js/methods/user.js +747 -0
- package/js/utils/utils.d.ts +20 -0
- package/js/utils/utils.js +286 -0
- package/js/utils/validator.d.ts +19 -0
- package/js/utils/validator.js +294 -0
- package/package.json +5 -3
|
@@ -0,0 +1,747 @@
|
|
|
1
|
+
import SkapiError from '../main/error';
|
|
2
|
+
import { CognitoUserAttribute, CognitoUser, AuthenticationDetails, CognitoUserPool } from 'amazon-cognito-identity-js';
|
|
3
|
+
import validator from '../utils/validator';
|
|
4
|
+
import { request } from './request';
|
|
5
|
+
import { MD5 } from '../utils/utils';
|
|
6
|
+
let cognitoUser = null;
|
|
7
|
+
export let userPool = null;
|
|
8
|
+
export function setUserPool(params) {
|
|
9
|
+
userPool = new CognitoUserPool(params);
|
|
10
|
+
}
|
|
11
|
+
export function authentication() {
|
|
12
|
+
if (!userPool)
|
|
13
|
+
throw new SkapiError('User pool is missing', { code: 'INVALID_REQUEST' });
|
|
14
|
+
const normalizeUserAttributes = (attr) => {
|
|
15
|
+
let user = {};
|
|
16
|
+
if (Array.isArray(attr)) {
|
|
17
|
+
let normalized_user_attribute_keys = {};
|
|
18
|
+
for (let i of attr) {
|
|
19
|
+
normalized_user_attribute_keys[i.Name] = i.Value;
|
|
20
|
+
if (i.Name === 'custom:service' && normalized_user_attribute_keys[i.Name] !== this.service) {
|
|
21
|
+
throw new SkapiError('The user is not registered to the service.', { code: 'INVALID_REQUEST' });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
attr = normalized_user_attribute_keys;
|
|
25
|
+
}
|
|
26
|
+
for (let k in attr) {
|
|
27
|
+
if (k.includes('custom:')) {
|
|
28
|
+
if (k === 'custom:service' && attr[k] !== this.service) {
|
|
29
|
+
throw new SkapiError('The user is not registered to the service.', { code: 'INVALID_REQUEST' });
|
|
30
|
+
}
|
|
31
|
+
user[k.replace('custom:', '')] = attr[k];
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
if (k === 'address') {
|
|
35
|
+
let addr_main = attr[k];
|
|
36
|
+
if (addr_main && typeof addr_main === 'object' && Object.keys(addr_main).length) {
|
|
37
|
+
if (addr_main?.formatted) {
|
|
38
|
+
try {
|
|
39
|
+
attr[k] = JSON.parse(addr_main.formatted);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
attr[k] = addr_main.formatted;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
user[k] = attr[k];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
for (let k of [
|
|
51
|
+
'address_public',
|
|
52
|
+
'birthdate_public',
|
|
53
|
+
'email_public',
|
|
54
|
+
'gender_public',
|
|
55
|
+
'phone_number_public',
|
|
56
|
+
'access_group'
|
|
57
|
+
]) {
|
|
58
|
+
if (k.includes('_public')) {
|
|
59
|
+
if (user.hasOwnProperty(k.split('_')[0]))
|
|
60
|
+
user[k] = user.hasOwnProperty(k) ? !!Number(user[k]) : false;
|
|
61
|
+
else
|
|
62
|
+
delete user[k];
|
|
63
|
+
}
|
|
64
|
+
else
|
|
65
|
+
user[k] = user.hasOwnProperty(k) ? Number(user[k]) : 0;
|
|
66
|
+
}
|
|
67
|
+
for (let k of [
|
|
68
|
+
'email',
|
|
69
|
+
'phone_number'
|
|
70
|
+
]) {
|
|
71
|
+
if (user.hasOwnProperty(k)) {
|
|
72
|
+
if (user[k + '_verified'] === true || user[k + '_verified'] === 'true') {
|
|
73
|
+
user[k + '_verified'] = true;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
user[k + '_verified'] = false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
delete user[k + '_verified'];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (let k of [
|
|
84
|
+
'aud',
|
|
85
|
+
{ from: 'auth_time', to: 'log' },
|
|
86
|
+
'cognito:username',
|
|
87
|
+
'event_id',
|
|
88
|
+
'exp',
|
|
89
|
+
'iat',
|
|
90
|
+
'iss',
|
|
91
|
+
'jti',
|
|
92
|
+
'origin_jti',
|
|
93
|
+
'secret_key',
|
|
94
|
+
{ from: 'sub', to: 'user_id' },
|
|
95
|
+
'token_use'
|
|
96
|
+
]) {
|
|
97
|
+
if (typeof k === 'string') {
|
|
98
|
+
delete user[k];
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
user[k.to] = user[k.from];
|
|
102
|
+
delete user[k.from];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
;
|
|
106
|
+
this.__user = user;
|
|
107
|
+
};
|
|
108
|
+
const getUser = () => {
|
|
109
|
+
return new Promise((res, rej) => {
|
|
110
|
+
if (!this.session) {
|
|
111
|
+
res(null);
|
|
112
|
+
}
|
|
113
|
+
if (cognitoUser === null) {
|
|
114
|
+
rej(new SkapiError('Invalid session', { code: 'INVALID_REQUEST' }));
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
cognitoUser.getUserAttributes((attrErr, attributes) => {
|
|
118
|
+
if (attrErr)
|
|
119
|
+
rej(attrErr);
|
|
120
|
+
else {
|
|
121
|
+
normalizeUserAttributes(attributes);
|
|
122
|
+
res(this.user);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
const getSession = (option) => {
|
|
129
|
+
let { refreshToken = false } = option || {};
|
|
130
|
+
return new Promise((res, rej) => {
|
|
131
|
+
cognitoUser = userPool?.getCurrentUser() || null;
|
|
132
|
+
if (cognitoUser === null) {
|
|
133
|
+
rej(null);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
cognitoUser.getSession((err, session) => {
|
|
137
|
+
if (err) {
|
|
138
|
+
rej(err);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (!session) {
|
|
142
|
+
rej(new SkapiError('Current session does not exist.', { code: 'INVALID_REQUEST' }));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (refreshToken || !session.isValid()) {
|
|
146
|
+
let signedOut = false;
|
|
147
|
+
try {
|
|
148
|
+
let idToken = session.getIdToken().payload;
|
|
149
|
+
if (idToken['custom:service'] !== this.service) {
|
|
150
|
+
cognitoUser.signOut();
|
|
151
|
+
this.session = null;
|
|
152
|
+
signedOut = true;
|
|
153
|
+
res(null);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
}
|
|
158
|
+
if (!signedOut) {
|
|
159
|
+
cognitoUser.refreshSession(session.getRefreshToken(), (refreshErr, refreshedSession) => {
|
|
160
|
+
if (refreshErr)
|
|
161
|
+
rej(refreshErr);
|
|
162
|
+
else {
|
|
163
|
+
if (refreshedSession.isValid()) {
|
|
164
|
+
this.session = refreshedSession;
|
|
165
|
+
normalizeUserAttributes(refreshedSession.getIdToken().payload);
|
|
166
|
+
res(refreshedSession);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
rej(new SkapiError('Invalid session.', { code: 'INVALID_REQUEST' }));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
let idToken = session.getIdToken().payload;
|
|
178
|
+
if (idToken['custom:service'] !== this.service) {
|
|
179
|
+
cognitoUser.signOut();
|
|
180
|
+
this.session = null;
|
|
181
|
+
res(null);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
this.session = session;
|
|
185
|
+
normalizeUserAttributes(idToken);
|
|
186
|
+
res(session);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
const createCognitoUser = async (email) => {
|
|
193
|
+
if (!email)
|
|
194
|
+
throw new SkapiError('E-Mail is required.', { code: 'INVALID_PARAMETER' });
|
|
195
|
+
let username = this.service + '-' + MD5.hash(email);
|
|
196
|
+
return {
|
|
197
|
+
cognitoUser: new CognitoUser({
|
|
198
|
+
Username: username,
|
|
199
|
+
Pool: userPool
|
|
200
|
+
}),
|
|
201
|
+
cognitoUsername: username
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
const authenticateUser = (email, password) => {
|
|
205
|
+
return new Promise((res, rej) => {
|
|
206
|
+
this.__request_signup_confirmation = null;
|
|
207
|
+
this.__disabledAccount = null;
|
|
208
|
+
createCognitoUser(email).then(initUser => {
|
|
209
|
+
cognitoUser = initUser.cognitoUser;
|
|
210
|
+
let username = initUser.cognitoUsername;
|
|
211
|
+
let authenticationDetails = new AuthenticationDetails({
|
|
212
|
+
Username: username,
|
|
213
|
+
Password: password
|
|
214
|
+
});
|
|
215
|
+
cognitoUser.authenticateUser(authenticationDetails, {
|
|
216
|
+
newPasswordRequired: (userAttributes, requiredAttributes) => {
|
|
217
|
+
this.__request_signup_confirmation = username;
|
|
218
|
+
rej(new SkapiError("User's signup confirmation is required.", { code: 'SIGNUP_CONFIRMATION_NEEDED' }));
|
|
219
|
+
},
|
|
220
|
+
onSuccess: (logged) => getSession().then(session => res(this.user)),
|
|
221
|
+
onFailure: (err) => {
|
|
222
|
+
let error = [err.message || 'Failed to authenticate user.', err?.code || 'INVALID_REQUEST'];
|
|
223
|
+
if (err.code === "NotAuthorizedException") {
|
|
224
|
+
if (err.message === "User is disabled.") {
|
|
225
|
+
this.__disabledAccount = username;
|
|
226
|
+
error = ['This account is disabled.', 'USER_IS_DISABLED'];
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
error = ['Incorrect username or password.', 'INCORRECT_USERNAME_OR_PASSWORD'];
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
let errCode = error[1];
|
|
233
|
+
let errMsg = error[0];
|
|
234
|
+
let customErr = error[0].split('#');
|
|
235
|
+
if (customErr.length > 1) {
|
|
236
|
+
customErr = customErr[customErr.length - 1].split(':');
|
|
237
|
+
errCode = customErr[0];
|
|
238
|
+
errMsg = customErr[1];
|
|
239
|
+
}
|
|
240
|
+
rej(new SkapiError(errMsg, { code: errCode, cause: err }));
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
};
|
|
246
|
+
return { getSession, authenticateUser, createCognitoUser, getUser };
|
|
247
|
+
}
|
|
248
|
+
export async function getProfile(options) {
|
|
249
|
+
await this.__connection;
|
|
250
|
+
try {
|
|
251
|
+
await authentication.bind(this)().getSession(options);
|
|
252
|
+
return this.user;
|
|
253
|
+
}
|
|
254
|
+
catch (err) {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
export async function checkAdmin() {
|
|
259
|
+
await this.__connection;
|
|
260
|
+
if (this.__user?.service === this.service) {
|
|
261
|
+
return this.__user?.owner === this.host;
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
await logout.bind(this)();
|
|
265
|
+
}
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
export async function logout(e) {
|
|
269
|
+
await this.__connection;
|
|
270
|
+
if (cognitoUser) {
|
|
271
|
+
cognitoUser.signOut();
|
|
272
|
+
}
|
|
273
|
+
let to_be_erased = {
|
|
274
|
+
'session': null,
|
|
275
|
+
'__startKeyHistory': {},
|
|
276
|
+
'__cached_requests': {},
|
|
277
|
+
'__user': null
|
|
278
|
+
};
|
|
279
|
+
for (let k in to_be_erased) {
|
|
280
|
+
this[k] = to_be_erased[k];
|
|
281
|
+
}
|
|
282
|
+
return 'SUCCESS: The user has been logged out.';
|
|
283
|
+
}
|
|
284
|
+
export async function resendSignupConfirmation(redirect) {
|
|
285
|
+
if (!this.__request_signup_confirmation) {
|
|
286
|
+
throw new SkapiError('Least one login attempt is required.', { code: 'INVALID_REQUEST' });
|
|
287
|
+
}
|
|
288
|
+
if (redirect) {
|
|
289
|
+
redirect = validator.Url(redirect);
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
redirect = undefined;
|
|
293
|
+
}
|
|
294
|
+
let resend = await request.bind(this)("confirm-signup", {
|
|
295
|
+
username: this.__request_signup_confirmation,
|
|
296
|
+
redirect
|
|
297
|
+
});
|
|
298
|
+
return resend;
|
|
299
|
+
}
|
|
300
|
+
export async function recoverAccount(redirect = false) {
|
|
301
|
+
if (typeof redirect === 'string') {
|
|
302
|
+
validator.Url(redirect);
|
|
303
|
+
}
|
|
304
|
+
else if (typeof redirect !== 'boolean') {
|
|
305
|
+
throw new SkapiError('Argument should be type: <boolean | string>.', { code: 'INVALID_REQUEST' });
|
|
306
|
+
}
|
|
307
|
+
if (!this.__disabledAccount) {
|
|
308
|
+
throw new SkapiError('Least one signin attempt of disabled account is required.', { code: 'INVALID_REQUEST' });
|
|
309
|
+
}
|
|
310
|
+
await request.bind(this)("recover-account", { username: this.__disabledAccount, redirect });
|
|
311
|
+
this.__disabledAccount = null;
|
|
312
|
+
return 'SUCCESS: Recovery e-mail has been sent.';
|
|
313
|
+
}
|
|
314
|
+
export async function login(form, option) {
|
|
315
|
+
await this.__connection;
|
|
316
|
+
if (option?.logout === false) {
|
|
317
|
+
let to_be_erased = {
|
|
318
|
+
'__startKeyHistory': {},
|
|
319
|
+
'__cached_requests': {}
|
|
320
|
+
};
|
|
321
|
+
for (let k in to_be_erased) {
|
|
322
|
+
this[k] = to_be_erased[k];
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
else {
|
|
326
|
+
await logout.bind(this)();
|
|
327
|
+
}
|
|
328
|
+
let params = validator.Params(form, {
|
|
329
|
+
email: (v) => validator.Email(v),
|
|
330
|
+
password: (v) => validator.Password(v)
|
|
331
|
+
}, ['email', 'password']);
|
|
332
|
+
return authentication.bind(this)().authenticateUser(params.email, params.password);
|
|
333
|
+
}
|
|
334
|
+
export async function signup(form, option) {
|
|
335
|
+
await this.logout();
|
|
336
|
+
let params = validator.Params(form || {}, {
|
|
337
|
+
email: (v) => validator.Email(v),
|
|
338
|
+
password: (v) => validator.Password(v),
|
|
339
|
+
name: 'string',
|
|
340
|
+
address: 'string',
|
|
341
|
+
gender: 'string',
|
|
342
|
+
birthdate: (v) => validator.Birthdate(v),
|
|
343
|
+
phone_number: (v) => validator.PhoneNumber(v),
|
|
344
|
+
email_public: ['boolean', () => false],
|
|
345
|
+
address_public: ['boolean', () => false],
|
|
346
|
+
gender_public: ['boolean', () => false],
|
|
347
|
+
birthdate_public: ['boolean', () => false],
|
|
348
|
+
phone_number_public: ['boolean', () => false],
|
|
349
|
+
misc: 'string'
|
|
350
|
+
}, ['email', 'password']);
|
|
351
|
+
option = validator.Params(option || {}, {
|
|
352
|
+
email_subscription: (v) => {
|
|
353
|
+
if (typeof v !== 'boolean') {
|
|
354
|
+
throw new SkapiError('"option.email_subscription" should be type: <boolean>.', { code: 'INVALID_PARAMETER' });
|
|
355
|
+
}
|
|
356
|
+
if (!option?.signup_confirmation) {
|
|
357
|
+
throw new SkapiError('"option.signup_confirmation" is required for email subscription.', { code: 'INVALID_PARAMETER' });
|
|
358
|
+
}
|
|
359
|
+
return v;
|
|
360
|
+
},
|
|
361
|
+
signup_confirmation: (v) => {
|
|
362
|
+
if (typeof v === 'string') {
|
|
363
|
+
return validator.Url(v);
|
|
364
|
+
}
|
|
365
|
+
else if (typeof v === 'boolean') {
|
|
366
|
+
return v;
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
throw new SkapiError('"option.signup_confirmation" should be type: <string | boolean>.', { code: 'INVALID_PARAMETER' });
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
login: (v) => {
|
|
373
|
+
if (typeof v === 'boolean') {
|
|
374
|
+
if (option.signup_confirmation && v) {
|
|
375
|
+
throw new SkapiError('"login" is not allowed when "option.signup_confirmation" is true.', { code: 'INVALID_PARAMETER' });
|
|
376
|
+
}
|
|
377
|
+
return v;
|
|
378
|
+
}
|
|
379
|
+
throw new SkapiError('"option.login" should be type: boolean.', { code: 'INVALID_PARAMETER' });
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
let logUser = option?.login || false;
|
|
383
|
+
let signup_confirmation = option?.signup_confirmation || false;
|
|
384
|
+
if (params.email_public && !signup_confirmation) {
|
|
385
|
+
throw new SkapiError('"option.signup_confirmation" should be true if "email_public" is set to true.', { code: 'INVALID_PARAMETER' });
|
|
386
|
+
}
|
|
387
|
+
params.signup_confirmation = signup_confirmation;
|
|
388
|
+
params.email_subscription = option?.email_subscription || false;
|
|
389
|
+
await request.bind(this)("signup", params);
|
|
390
|
+
if (signup_confirmation) {
|
|
391
|
+
let u = await authentication.bind(this)().createCognitoUser(params.email);
|
|
392
|
+
cognitoUser = u.cognitoUser;
|
|
393
|
+
this.__request_signup_confirmation = u.cognitoUsername;
|
|
394
|
+
return "SUCCESS: The account has been created. User's signup confirmation is required.";
|
|
395
|
+
}
|
|
396
|
+
if (logUser) {
|
|
397
|
+
return login.bind(this)({ email: params.email, password: params.password });
|
|
398
|
+
}
|
|
399
|
+
return 'SUCCESS: The account has been created.';
|
|
400
|
+
}
|
|
401
|
+
export async function disableAccount() {
|
|
402
|
+
await this.__connection;
|
|
403
|
+
let result = await request.bind(this)('remove-account', { disable: this.__user.user_id }, { auth: true });
|
|
404
|
+
await logout.bind(this)();
|
|
405
|
+
return result;
|
|
406
|
+
}
|
|
407
|
+
export async function resetPassword(form, option) {
|
|
408
|
+
await this.__connection;
|
|
409
|
+
let params = validator.Params(form, {
|
|
410
|
+
email: (v) => validator.Email(v),
|
|
411
|
+
code: ['number', 'string'],
|
|
412
|
+
new_password: (v) => validator.Password(v)
|
|
413
|
+
}, ['email', 'code', 'new_password']);
|
|
414
|
+
let code = params.code, new_password = params.new_password;
|
|
415
|
+
if (typeof code === 'number') {
|
|
416
|
+
code = code.toString();
|
|
417
|
+
}
|
|
418
|
+
return new Promise(async (res, rej) => {
|
|
419
|
+
let cognitoUser = (await authentication.bind(this)().createCognitoUser(params.email)).cognitoUser;
|
|
420
|
+
cognitoUser.confirmPassword(code, new_password, {
|
|
421
|
+
onSuccess: result => {
|
|
422
|
+
res("SUCCESS: New password has been set.");
|
|
423
|
+
},
|
|
424
|
+
onFailure: (err) => {
|
|
425
|
+
rej(new SkapiError(err?.message || 'Failed to reset password.', { code: err?.code }));
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
async function verifyAttribute(attribute, form) {
|
|
431
|
+
await this.__connection;
|
|
432
|
+
let code;
|
|
433
|
+
if (!cognitoUser) {
|
|
434
|
+
throw new SkapiError('The user has to be logged in.', { code: 'INVALID_REQUEST' });
|
|
435
|
+
}
|
|
436
|
+
if (attribute === 'email' || attribute === 'phone_number') {
|
|
437
|
+
if (!this.__user.hasOwnProperty(attribute)) {
|
|
438
|
+
throw new SkapiError(`No ${attribute === 'email' ? 'e-mail' : 'phone number'} to verify`, { code: 'INVALID_REQUEST' });
|
|
439
|
+
}
|
|
440
|
+
if (this.__user?.[`${attribute}_verified`]) {
|
|
441
|
+
return `SUCCESS: "${attribute}" is verified.`;
|
|
442
|
+
}
|
|
443
|
+
code = (form ? validator.Params(form, {
|
|
444
|
+
code: ['string']
|
|
445
|
+
}) : {}).code || '';
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
return new Promise((res, rej) => {
|
|
451
|
+
let callback = {
|
|
452
|
+
onSuccess: (result) => {
|
|
453
|
+
if (code) {
|
|
454
|
+
authentication.bind(this)().getSession({ refreshToken: true }).then(() => {
|
|
455
|
+
if (this.__user) {
|
|
456
|
+
this.__user[attribute + '_verified'] = true;
|
|
457
|
+
}
|
|
458
|
+
res(`SUCCESS: "${attribute}" is verified.`);
|
|
459
|
+
}).catch(err => {
|
|
460
|
+
rej(err);
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
res('SUCCESS: Verification code has been sent.');
|
|
465
|
+
}
|
|
466
|
+
},
|
|
467
|
+
onFailure: (err) => {
|
|
468
|
+
rej(new SkapiError(err.message || 'Failed to request verification code.', {
|
|
469
|
+
code: err?.code
|
|
470
|
+
}));
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
if (code) {
|
|
474
|
+
cognitoUser?.verifyAttribute(attribute, code, callback);
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
callback.inputVerificationCode = null;
|
|
478
|
+
cognitoUser?.getAttributeVerificationCode(attribute, callback);
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
export function verifyPhoneNumber(form) {
|
|
483
|
+
return verifyAttribute.bind(this)('phone_number', form);
|
|
484
|
+
}
|
|
485
|
+
export function verifyEmail(form) {
|
|
486
|
+
return verifyAttribute.bind(this)('email', form);
|
|
487
|
+
}
|
|
488
|
+
export async function forgotPassword(form, option) {
|
|
489
|
+
await this.__connection;
|
|
490
|
+
let params = validator.Params(form, {
|
|
491
|
+
email: (v) => validator.Email(v)
|
|
492
|
+
}, ['email']);
|
|
493
|
+
return new Promise(async (res, rej) => {
|
|
494
|
+
let cognitoUser = (await authentication.bind(this)().createCognitoUser(params.email)).cognitoUser;
|
|
495
|
+
cognitoUser.forgotPassword({
|
|
496
|
+
onSuccess: result => {
|
|
497
|
+
res("SUCCESS: Verification code has been sent.");
|
|
498
|
+
},
|
|
499
|
+
onFailure: (err) => {
|
|
500
|
+
rej(new SkapiError(err?.message || 'Failed to send verification code.', { code: err?.code || 'ERROR' }));
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
export async function changePassword(params) {
|
|
506
|
+
await this.__connection;
|
|
507
|
+
if (!this.session) {
|
|
508
|
+
throw new SkapiError('User login is required.', { code: 'INVALID_REQUEST' });
|
|
509
|
+
}
|
|
510
|
+
let p = validator.Params(params, {
|
|
511
|
+
'current_password': 'string',
|
|
512
|
+
'new_password': 'string'
|
|
513
|
+
});
|
|
514
|
+
if (!p?.current_password) {
|
|
515
|
+
throw new SkapiError('"current_password" is required to change password.', { code: 'INVALID_PARAMETER' });
|
|
516
|
+
}
|
|
517
|
+
if (!p?.new_password) {
|
|
518
|
+
throw new SkapiError('"new_password" is required to change password.', { code: 'INVALID_PARAMETER' });
|
|
519
|
+
}
|
|
520
|
+
validator.Password(p.current_password);
|
|
521
|
+
validator.Password(p.new_password);
|
|
522
|
+
return new Promise((res, rej) => {
|
|
523
|
+
cognitoUser.changePassword(p.current_password, p.new_password, (err, result) => {
|
|
524
|
+
if (err) {
|
|
525
|
+
if (err?.code === "InvalidParameterException") {
|
|
526
|
+
rej(new SkapiError('Invalid password parameter.', { code: 'INVALID_PARAMETER' }));
|
|
527
|
+
}
|
|
528
|
+
else if (err?.code === "NotAuthorizedException") {
|
|
529
|
+
rej(new SkapiError('Incorrect password.', { code: 'INVALID_REQUEST' }));
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
rej(new SkapiError(err?.message || 'Failed to change user password.', { code: err?.code || err?.name }));
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
res('SUCCESS: Password has been changed.');
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
export async function updateProfile(form, option) {
|
|
540
|
+
await this.__connection;
|
|
541
|
+
if (!this.session) {
|
|
542
|
+
throw new SkapiError('User login is required.', { code: 'INVALID_REQUEST' });
|
|
543
|
+
}
|
|
544
|
+
let params = validator.Params(form || {}, {
|
|
545
|
+
email: (v) => validator.Email(v),
|
|
546
|
+
address: (v) => {
|
|
547
|
+
if (!v)
|
|
548
|
+
return '';
|
|
549
|
+
if (typeof v === 'string') {
|
|
550
|
+
return v;
|
|
551
|
+
}
|
|
552
|
+
if (typeof v === 'object') {
|
|
553
|
+
return JSON.stringify(v);
|
|
554
|
+
}
|
|
555
|
+
return '';
|
|
556
|
+
},
|
|
557
|
+
name: 'string',
|
|
558
|
+
gender: 'string',
|
|
559
|
+
birthdate: (v) => validator.Birthdate(v),
|
|
560
|
+
phone_number: (v) => validator.PhoneNumber(v),
|
|
561
|
+
email_public: 'boolean',
|
|
562
|
+
phone_number_public: 'boolean',
|
|
563
|
+
address_public: 'boolean',
|
|
564
|
+
gender_public: 'boolean',
|
|
565
|
+
birthdate_public: 'boolean',
|
|
566
|
+
misc: 'string'
|
|
567
|
+
});
|
|
568
|
+
if (params && typeof params === 'object' && !Object.keys(params).length) {
|
|
569
|
+
return this.user;
|
|
570
|
+
}
|
|
571
|
+
if (params.email) {
|
|
572
|
+
params['preferred_username'] = this.service + '-' + MD5.hash(params.email);
|
|
573
|
+
}
|
|
574
|
+
let collision = [
|
|
575
|
+
['email_public', 'email_verified', "User's E-Mail should be verified to set"],
|
|
576
|
+
['phone_number_public', 'phone_number_verified', "User's phone number should be verified to set"]
|
|
577
|
+
];
|
|
578
|
+
if (this.__user) {
|
|
579
|
+
for (let c of collision) {
|
|
580
|
+
if (params[c[0]] && !this.__user[c[1]]) {
|
|
581
|
+
throw new SkapiError(`${c[2]} "${c[0]}" to true.`, { code: 'INVALID_REQUEST' });
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
let toRemove = [];
|
|
586
|
+
for (let k in params) {
|
|
587
|
+
if (params[k] === this.user[k]) {
|
|
588
|
+
toRemove.push(k);
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
let customAttr = [
|
|
592
|
+
'email_public',
|
|
593
|
+
'phone_number_public',
|
|
594
|
+
'address_public',
|
|
595
|
+
'gender_public',
|
|
596
|
+
'birthdate_public',
|
|
597
|
+
'misc'
|
|
598
|
+
];
|
|
599
|
+
if (customAttr.includes(k)) {
|
|
600
|
+
let parseValue = params[k];
|
|
601
|
+
if (typeof parseValue === 'boolean') {
|
|
602
|
+
parseValue = parseValue ? '1' : '0';
|
|
603
|
+
}
|
|
604
|
+
params['custom:' + k] = parseValue;
|
|
605
|
+
toRemove.push(k);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
for (let k of toRemove) {
|
|
609
|
+
delete params[k];
|
|
610
|
+
}
|
|
611
|
+
if (params && typeof params === 'object' && Object.keys(params).length) {
|
|
612
|
+
let toSet = [];
|
|
613
|
+
for (let key in params) {
|
|
614
|
+
toSet.push(new CognitoUserAttribute({
|
|
615
|
+
Name: key,
|
|
616
|
+
Value: params[key]
|
|
617
|
+
}));
|
|
618
|
+
}
|
|
619
|
+
await new Promise((res, rej) => {
|
|
620
|
+
cognitoUser?.updateAttributes(toSet, (err, result) => {
|
|
621
|
+
if (err) {
|
|
622
|
+
rej([
|
|
623
|
+
err?.code || err?.name,
|
|
624
|
+
err?.message || `Failed to update user settings.`
|
|
625
|
+
]);
|
|
626
|
+
}
|
|
627
|
+
res(result);
|
|
628
|
+
});
|
|
629
|
+
});
|
|
630
|
+
await authentication.bind(this)().getSession({ refreshToken: true });
|
|
631
|
+
return this.user;
|
|
632
|
+
}
|
|
633
|
+
return this.user;
|
|
634
|
+
}
|
|
635
|
+
export async function getUsers(params, fetchOptions) {
|
|
636
|
+
if (!params) {
|
|
637
|
+
params = {
|
|
638
|
+
searchFor: 'timestamp',
|
|
639
|
+
condition: '>',
|
|
640
|
+
value: 0
|
|
641
|
+
};
|
|
642
|
+
if (!fetchOptions) {
|
|
643
|
+
fetchOptions = {};
|
|
644
|
+
}
|
|
645
|
+
fetchOptions.ascending = false;
|
|
646
|
+
}
|
|
647
|
+
let isAdmin = await checkAdmin.bind(this)();
|
|
648
|
+
if (isAdmin && !params.hasOwnProperty('service')) {
|
|
649
|
+
throw new SkapiError('Service ID is required.', { code: 'INVALID_PARAMETER' });
|
|
650
|
+
}
|
|
651
|
+
const searchForTypes = {
|
|
652
|
+
'user_id': (v) => validator.UserId(v),
|
|
653
|
+
'name': 'string',
|
|
654
|
+
'email': (v) => validator.Email(v),
|
|
655
|
+
'phone_number': (v) => validator.PhoneNumber(v),
|
|
656
|
+
'address': 'string',
|
|
657
|
+
'gender': 'string',
|
|
658
|
+
'birthdate': (v) => validator.Birthdate(v),
|
|
659
|
+
'locale': (v) => {
|
|
660
|
+
if (typeof v !== 'string' || typeof v === 'string' && v.length > 5) {
|
|
661
|
+
throw new SkapiError('Value of "locale" should be a country code.', { code: 'INVALID_PARAMETER' });
|
|
662
|
+
}
|
|
663
|
+
return v;
|
|
664
|
+
},
|
|
665
|
+
'timestamp': 'number',
|
|
666
|
+
'access_group': 'number',
|
|
667
|
+
'approved': (v) => {
|
|
668
|
+
if (v) {
|
|
669
|
+
return 'by_admin:approved';
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
return 'by_admin:suspended';
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
let required = ['searchFor', 'value'];
|
|
677
|
+
params = validator.Params(params, {
|
|
678
|
+
searchFor: [
|
|
679
|
+
'user_id',
|
|
680
|
+
'name',
|
|
681
|
+
'email',
|
|
682
|
+
'phone_number',
|
|
683
|
+
'address',
|
|
684
|
+
'gender',
|
|
685
|
+
'birthdate',
|
|
686
|
+
'locale',
|
|
687
|
+
'subscribers',
|
|
688
|
+
'timestamp',
|
|
689
|
+
'access_group',
|
|
690
|
+
'approved'
|
|
691
|
+
],
|
|
692
|
+
condition: ['>', '>=', '=', '<', '<=', 'gt', 'gte', 'eq', 'lt', 'lte', () => '='],
|
|
693
|
+
value: (v) => {
|
|
694
|
+
let checker = searchForTypes[params.searchFor];
|
|
695
|
+
if (typeof checker === 'function') {
|
|
696
|
+
return checker(v);
|
|
697
|
+
}
|
|
698
|
+
else if (typeof v !== checker) {
|
|
699
|
+
throw new SkapiError(`Value does not match the type of "${params.searchFor}" index.`, { code: 'INVALID_PARAMETER' });
|
|
700
|
+
}
|
|
701
|
+
return v;
|
|
702
|
+
},
|
|
703
|
+
range: (v) => {
|
|
704
|
+
let checker = searchForTypes[params.searchFor];
|
|
705
|
+
if (typeof checker === 'function') {
|
|
706
|
+
return checker(v);
|
|
707
|
+
}
|
|
708
|
+
else if (typeof v !== checker) {
|
|
709
|
+
throw new SkapiError(`Range does not match the type of "${params.searchFor}" index.`, { code: 'INVALID_PARAMETER' });
|
|
710
|
+
}
|
|
711
|
+
return v;
|
|
712
|
+
}
|
|
713
|
+
}, required);
|
|
714
|
+
if (params?.condition && params?.condition !== '=' && params.hasOwnProperty('range')) {
|
|
715
|
+
throw new SkapiError('Conditions does not apply on range search.', { code: 'INVALID_PARAMETER' });
|
|
716
|
+
}
|
|
717
|
+
if (params.searchFor === 'user_id' && params.condition !== '=') {
|
|
718
|
+
throw new SkapiError(`Conditions are not allowed on "user_id"`, { code: 'INVALID_PARAMETER' });
|
|
719
|
+
}
|
|
720
|
+
if (params.searchFor === 'access_group') {
|
|
721
|
+
params.searchFor = 'group';
|
|
722
|
+
}
|
|
723
|
+
if (typeof params?.value === 'string' && !params?.value) {
|
|
724
|
+
throw new SkapiError('Value should not be an empty string.', { code: 'INVALID_PARAMETER' });
|
|
725
|
+
}
|
|
726
|
+
if (typeof params?.searchFor === 'string' && !params?.searchFor) {
|
|
727
|
+
throw new SkapiError('"searchFor" should not be an empty string.', { code: 'INVALID_PARAMETER' });
|
|
728
|
+
}
|
|
729
|
+
return request.bind(this)('get-users', params, { auth: true, fetchOptions });
|
|
730
|
+
}
|
|
731
|
+
export async function lastVerifiedEmail(params) {
|
|
732
|
+
await this.__connection;
|
|
733
|
+
let res = await request.bind(this)('last-verified-email', params, { auth: true });
|
|
734
|
+
if (res.includes('SUCCESS')) {
|
|
735
|
+
await authentication.bind(this)().getSession({ refreshToken: true });
|
|
736
|
+
return this.user;
|
|
737
|
+
}
|
|
738
|
+
return res;
|
|
739
|
+
}
|
|
740
|
+
export async function requestUsernameChange(params) {
|
|
741
|
+
await this.__connection;
|
|
742
|
+
params = validator.Params(params, {
|
|
743
|
+
username: validator.Email,
|
|
744
|
+
redirect: validator.Url
|
|
745
|
+
}, ['username']);
|
|
746
|
+
return await request.bind(this)('request-username-change', params, { auth: true });
|
|
747
|
+
}
|