Added Password Chagne
This commit is contained in:
parent
00b3020800
commit
5efc796edc
|
|
@ -5,6 +5,18 @@ export class Message
|
|||
type:MessageType;
|
||||
content:string;
|
||||
|
||||
static hasError( messages:Message[] ):boolean
|
||||
{
|
||||
if ( ! messages || messages.length == 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let errorMessage = messages.find( m => MessageTypes.Error == m.type );
|
||||
|
||||
return ! ( ! errorMessage );
|
||||
}
|
||||
|
||||
static with( type:MessageType, content:string )
|
||||
{
|
||||
let message = new Message();
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { Message } from "../../browser/messages/Message";
|
|||
import { UserData } from "./UserData";
|
||||
import { UAParser } from 'ua-parser-js';
|
||||
import { LocationData } from "./location/LocationData";
|
||||
import { className } from "../../browser/tools/TypeUtilities";
|
||||
|
||||
export enum RequestType
|
||||
{
|
||||
|
|
@ -85,11 +86,11 @@ export abstract class RequestHandler
|
|||
|
||||
for ( let r of this._ums.globalRequirements )
|
||||
{
|
||||
let fullfilled = await r.handle( request, reply );
|
||||
let messages = await r.handle( request, reply );
|
||||
|
||||
if ( ! fullfilled )
|
||||
if ( Message.hasError( messages ) )
|
||||
{
|
||||
RJLog.log( "Global Requirement not fullfilled", r );
|
||||
RJLog.log( "Global Requirement not fullfilled: ", className( r ), messages.map( m => JSON.stringify( m ) ).join( ", " ) );
|
||||
|
||||
return this.sendError( "Error during global requirements check" );
|
||||
}
|
||||
|
|
@ -97,11 +98,12 @@ export abstract class RequestHandler
|
|||
|
||||
for ( let r of this.requirements )
|
||||
{
|
||||
let fullfilled = await r.handle( request, reply );
|
||||
let messages = await r.handle( request, reply );
|
||||
|
||||
if ( ! fullfilled )
|
||||
if ( Message.hasError( messages ) )
|
||||
{
|
||||
RJLog.log( "Requirement not fullfilled", r );
|
||||
RJLog.log( messages[ 0 ].content );
|
||||
RJLog.log( "Requirement not fullfilled: ", className( r ), messages.map( m => JSON.stringify( m ) ).join( ", " ) );
|
||||
|
||||
return this.sendError( "Error during requirements check" );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export class UserDB
|
|||
_pendingUsers:UserData[] = [];
|
||||
|
||||
_signUpTokens = new Map<string,Token>();
|
||||
_passwordChangeTokens = new Map<string,Token>();
|
||||
|
||||
|
||||
|
||||
|
|
@ -62,6 +63,15 @@ export class UserDB
|
|||
return Promise.resolve( token );
|
||||
}
|
||||
|
||||
async createPasswordChange( userData:UserData, ip:string ):Promise<Token>
|
||||
{
|
||||
let token = await this._ums.tokenDB.create( ip );
|
||||
|
||||
this._passwordChangeTokens.set( userData.id, token );
|
||||
|
||||
return Promise.resolve( token );
|
||||
}
|
||||
|
||||
async byID( id:string )
|
||||
{
|
||||
return this.users.find( u => u.id === id );
|
||||
|
|
@ -98,6 +108,8 @@ export class UserDB
|
|||
if ( isValid )
|
||||
{
|
||||
Arrays.remove( this._pendingUsers, user );
|
||||
this._signUpTokens.delete( userID );
|
||||
this._ums.tokenDB._tokens.delete( token.id );
|
||||
this.users.push( user );
|
||||
|
||||
await this.save();
|
||||
|
|
@ -107,6 +119,37 @@ export class UserDB
|
|||
return Promise.resolve( isValid );
|
||||
}
|
||||
|
||||
async changePassword( userID:string, tokenID:string, password:string, ip:string ):Promise<boolean>
|
||||
{
|
||||
if ( ! this._passwordChangeTokens.has( userID ) )
|
||||
{
|
||||
return Promise.resolve( false );
|
||||
}
|
||||
|
||||
let token = this._passwordChangeTokens.get( userID );
|
||||
|
||||
if ( token.id != tokenID )
|
||||
{
|
||||
return Promise.resolve( false );
|
||||
}
|
||||
|
||||
let isValid = await this._ums.tokenDB.validate( token, ip );
|
||||
|
||||
if ( isValid )
|
||||
{
|
||||
this._passwordChangeTokens.delete( userID );
|
||||
this._ums.tokenDB._tokens.delete( token.id );
|
||||
let userData = await this._ums.userDB.byID( userID );
|
||||
userData.hashedPassword = await CryptIO.hash( password );
|
||||
|
||||
await this.save();
|
||||
|
||||
}
|
||||
|
||||
return Promise.resolve( isValid );
|
||||
}
|
||||
|
||||
|
||||
async login( email:string, password:string, ip:string ):Promise<Token>
|
||||
{
|
||||
let user = await this.byEmail( email );
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@ export class UserManagementServerSettings
|
|||
|
||||
// User settings
|
||||
maxLogins:number = 5;
|
||||
signUpConfirmationURL:string;
|
||||
changePasswordURL:string;
|
||||
|
||||
|
||||
static makeAllPathsAbsolute( settings:UserManagementServerSettings )
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
import { RequestHandler } from "../RequestHandler";
|
||||
import { ChangePasswordHandler } from "./_/change-password";
|
||||
import { ConfirmSignUpHandler } from "./_/confirm-signup";
|
||||
import { InfoHandler } from "./_/info";
|
||||
import { LoginHandler } from "./_/login";
|
||||
import { LogoutHandler } from "./_/logout";
|
||||
import { RequestPasswordChangeHandler } from "./_/request-password-change";
|
||||
import { SignUpHandler } from "./_/signup";
|
||||
|
||||
export class HandlerGroups
|
||||
{
|
||||
static get UserDefaultHandlers():RequestHandler[]
|
||||
{
|
||||
let defaultHandlers:RequestHandler[] =
|
||||
[
|
||||
new SignUpHandler(),
|
||||
new ConfirmSignUpHandler(),
|
||||
|
||||
new LoginHandler(),
|
||||
new LogoutHandler(),
|
||||
new InfoHandler(),
|
||||
|
||||
new RequestPasswordChangeHandler(),
|
||||
new ChangePasswordHandler()
|
||||
];
|
||||
|
||||
return defaultHandlers;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import { CryptIO } from "../../../crypt/CryptIO";
|
||||
import { RJLog } from "../../../log/RJLog";
|
||||
import { RequestHandler, RequestType } from "../../RequestHandler";
|
||||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
|
||||
export class ChangePasswordHandler extends RequestHandler
|
||||
{
|
||||
static url = "/change-password";
|
||||
constructor(){ super( RequestType.POST, ChangePasswordHandler.url ); }
|
||||
|
||||
async _handle( request:FastifyRequest, reply:FastifyReply )
|
||||
{
|
||||
let requestBody = request.body;
|
||||
let { id, password, token } = requestBody as { id: string; password:string, token: string, };
|
||||
|
||||
if ( ! id || ! token || ! password)
|
||||
{
|
||||
return this.sendError( "Missing id, token or password" );
|
||||
}
|
||||
|
||||
let result = await this.userDB.changePassword( id, token, password, this.ip );
|
||||
|
||||
if ( ! result )
|
||||
{
|
||||
return this.sendError( "Password change failed" );
|
||||
}
|
||||
|
||||
return this.sendInfo( "Changed password" );
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { CryptIO } from "../../crypt/CryptIO";
|
||||
import { RJLog } from "../../log/RJLog";
|
||||
import { RequestHandler, RequestType } from "../RequestHandler";
|
||||
import { CryptIO } from "../../../crypt/CryptIO";
|
||||
import { RJLog } from "../../../log/RJLog";
|
||||
import { RequestHandler, RequestType } from "../../RequestHandler";
|
||||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
|
||||
export class ConfirmSignUpHandler extends RequestHandler
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
import { RegExpUtility } from "../../../browser/text/RegExpUtitlity";
|
||||
import { CryptIO } from "../../crypt/CryptIO";
|
||||
import { RJLog } from "../../log/RJLog";
|
||||
import { RequestHandler, RequestType } from "../RequestHandler";
|
||||
import { RegExpUtility } from "../../../../browser/text/RegExpUtitlity";
|
||||
import { CryptIO } from "../../../crypt/CryptIO";
|
||||
import { RJLog } from "../../../log/RJLog";
|
||||
import { RequestHandler, RequestType } from "../../RequestHandler";
|
||||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { VariableReplacer, Variables } from "../../../browser/text/replacing/VariableReplacer";
|
||||
import { VariableReplacer, Variables } from "../../../../browser/text/replacing/VariableReplacer";
|
||||
import { ConfirmSignUpHandler } from "./confirm-signup";
|
||||
import { Session } from "../Session";
|
||||
import { LocationData } from "../location/LocationData";
|
||||
import { ISOTimeStamp } from "../../../browser/date/ISOTimeStamp";
|
||||
import { DateFormatter } from "../../../browser/date/DateFormatter";
|
||||
import { Session } from "../../Session";
|
||||
import { LocationData } from "../../location/LocationData";
|
||||
import { ISOTimeStamp } from "../../../../browser/date/ISOTimeStamp";
|
||||
import { DateFormatter } from "../../../../browser/date/DateFormatter";
|
||||
import { UAParser } from "ua-parser-js";
|
||||
import { UserIsLoggedIn } from "../requirements/user/UserIsLoggedIn";
|
||||
import { UserIsLoggedIn } from "../../requirements/user/UserIsLoggedIn";
|
||||
|
||||
export class UserLoginDataInfo
|
||||
{
|
||||
|
|
@ -56,7 +56,7 @@ export class InfoHandler extends RequestHandler
|
|||
}
|
||||
);
|
||||
|
||||
let info = { name:user.name, email:user.email, lastLogins:lastLogins };
|
||||
let info = { name:user.name, id:user.id, email:user.email, lastLogins:lastLogins };
|
||||
|
||||
return this.sendDataInfo( "User Info", info );
|
||||
}
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
import { RegExpUtility } from "../../../browser/text/RegExpUtitlity";
|
||||
import { CryptIO } from "../../crypt/CryptIO";
|
||||
import { RJLog } from "../../log/RJLog";
|
||||
import { RequestHandler, RequestType } from "../RequestHandler";
|
||||
import { RegExpUtility } from "../../../../browser/text/RegExpUtitlity";
|
||||
import { CryptIO } from "../../../crypt/CryptIO";
|
||||
import { RJLog } from "../../../log/RJLog";
|
||||
import { RequestHandler, RequestType } from "../../RequestHandler";
|
||||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { VariableReplacer, Variables } from "../../../browser/text/replacing/VariableReplacer";
|
||||
import { VariableReplacer, Variables } from "../../../../browser/text/replacing/VariableReplacer";
|
||||
import { ConfirmSignUpHandler } from "./confirm-signup";
|
||||
import { Session } from "../Session";
|
||||
import { UserLoginData } from "../UserData";
|
||||
import { ISOTimeStamp } from "../../../browser/date/ISOTimeStamp";
|
||||
import { Arrays } from "../../../browser/tools/Arrays";
|
||||
import { Session } from "../../Session";
|
||||
import { UserLoginData } from "../../UserData";
|
||||
import { ISOTimeStamp } from "../../../../browser/date/ISOTimeStamp";
|
||||
import { Arrays } from "../../../../browser/tools/Arrays";
|
||||
|
||||
export class LoginHandler extends RequestHandler
|
||||
{
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import { RegExpUtility } from "../../../browser/text/RegExpUtitlity";
|
||||
import { CryptIO } from "../../crypt/CryptIO";
|
||||
import { RJLog } from "../../log/RJLog";
|
||||
import { RequestHandler, RequestType } from "../RequestHandler";
|
||||
import { RegExpUtility } from "../../../../browser/text/RegExpUtitlity";
|
||||
import { CryptIO } from "../../../crypt/CryptIO";
|
||||
import { RJLog } from "../../../log/RJLog";
|
||||
import { RequestHandler, RequestType } from "../../RequestHandler";
|
||||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { VariableReplacer, Variables } from "../../../browser/text/replacing/VariableReplacer";
|
||||
import { VariableReplacer, Variables } from "../../../../browser/text/replacing/VariableReplacer";
|
||||
import { ConfirmSignUpHandler } from "./confirm-signup";
|
||||
import { Session } from "../Session";
|
||||
import { UserIsLoggedIn } from "../requirements/user/UserIsLoggedIn";
|
||||
import { Session } from "../../Session";
|
||||
import { UserIsLoggedIn } from "../../requirements/user/UserIsLoggedIn";
|
||||
|
||||
export class LogoutHandler extends RequestHandler
|
||||
{
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
import { VariableReplacer, Variables } from "../../../../browser/text/replacing/VariableReplacer";
|
||||
import { CryptIO } from "../../../crypt/CryptIO";
|
||||
import { RJLog } from "../../../log/RJLog";
|
||||
import { RequestHandler, RequestType } from "../../RequestHandler";
|
||||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
|
||||
export class RequestPasswordChangeHandler extends RequestHandler
|
||||
{
|
||||
static url = "/request-password-change";
|
||||
constructor(){ super( RequestType.POST, RequestPasswordChangeHandler.url ); }
|
||||
|
||||
static USER_NAME = "USER_NAME";
|
||||
static CHANGE_PASSWORD_LINK = "CONFIRMATION_LINK";
|
||||
|
||||
static EMAIL_TITLE = "Password Change Request";
|
||||
static EMAIL_MESSAGE =
|
||||
`
|
||||
<b>Hi {{${RequestPasswordChangeHandler.USER_NAME}}}!</b>
|
||||
<br>
|
||||
You requested to change your password, here is your link:
|
||||
<br>
|
||||
<a href="{{${RequestPasswordChangeHandler.CHANGE_PASSWORD_LINK}}}">CHANGE PASSWORD</a>
|
||||
<br>
|
||||
<br>
|
||||
Cheers!
|
||||
|
||||
`
|
||||
|
||||
async _handle( request:FastifyRequest, reply:FastifyReply )
|
||||
{
|
||||
let requestBody = request.body;
|
||||
let { email } = requestBody as { email:string };
|
||||
|
||||
if ( ! email )
|
||||
{
|
||||
return this.sendError( "Missing email" );
|
||||
}
|
||||
|
||||
let userData = await this.userDB.byEmail( email );
|
||||
|
||||
if ( ! userData )
|
||||
{
|
||||
return this.sendError( "Email not found" );
|
||||
}
|
||||
|
||||
let token = await this.userDB.createPasswordChange( userData, this.ip );
|
||||
|
||||
let changePasswordURL = this._ums._settings.changePasswordURL;
|
||||
|
||||
let id = userData.id;
|
||||
|
||||
let link = `${changePasswordURL}?id=${id}&token=${token.id}`;
|
||||
|
||||
await this.sendPasswordChangeEmail( email, userData.name, link );
|
||||
|
||||
return this.sendInfo( "Send password change email" );
|
||||
}
|
||||
|
||||
async sendPasswordChangeEmail( email:string, userName:string, link:string ):Promise<void>
|
||||
{
|
||||
|
||||
let variables:Variables = {
|
||||
[ RequestPasswordChangeHandler.USER_NAME ] : userName,
|
||||
[ RequestPasswordChangeHandler.CHANGE_PASSWORD_LINK ] : link
|
||||
};
|
||||
|
||||
let message = VariableReplacer.replace( RequestPasswordChangeHandler.EMAIL_MESSAGE, variables, "{{", "}}" );
|
||||
let title = VariableReplacer.replace( RequestPasswordChangeHandler.EMAIL_TITLE, variables, "{{", "}}" );
|
||||
|
||||
await this._ums.sendEmail( email, title, message );
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
import { RegExpUtility } from "../../../browser/text/RegExpUtitlity";
|
||||
import { CryptIO } from "../../crypt/CryptIO";
|
||||
import { RJLog } from "../../log/RJLog";
|
||||
import { RequestHandler, RequestType } from "../RequestHandler";
|
||||
import { RegExpUtility } from "../../../../browser/text/RegExpUtitlity";
|
||||
import { CryptIO } from "../../../crypt/CryptIO";
|
||||
import { RJLog } from "../../../log/RJLog";
|
||||
import { RequestHandler, RequestType } from "../../RequestHandler";
|
||||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { VariableReplacer, Variables } from "../../../browser/text/replacing/VariableReplacer";
|
||||
import { VariableReplacer, Variables } from "../../../../browser/text/replacing/VariableReplacer";
|
||||
import { ConfirmSignUpHandler } from "./confirm-signup";
|
||||
|
||||
export class SignUpHandler extends RequestHandler
|
||||
|
|
@ -55,19 +55,19 @@ export class SignUpHandler extends RequestHandler
|
|||
|
||||
let token = await this.userDB.createSignUpConfirmation( userData, this.ip );
|
||||
|
||||
let confirmationURL = this._ums._settings.url + ConfirmSignUpHandler.url;
|
||||
let confirmationURL = this._ums._settings.signUpConfirmationURL;
|
||||
|
||||
let id = userData.id;
|
||||
|
||||
let link = `${confirmationURL}?id=${id}&token=${token.id}`;
|
||||
|
||||
await this.sendEmail( email, userName, link );
|
||||
await this.sendSignUpEmail( email, userName, link );
|
||||
|
||||
|
||||
return this.sendInfo( "User created, email sent" );
|
||||
}
|
||||
|
||||
async sendEmail( email:string, userName:string, link:string ):Promise<void>
|
||||
async sendSignUpEmail( email:string, userName:string, link:string ):Promise<void>
|
||||
{
|
||||
|
||||
let variables:Variables = {
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { RequestHandler } from "../RequestHandler";
|
||||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { UserManagementServer } from "../UserManagementServer";
|
||||
import { Message } from "../../../browser/messages/Message";
|
||||
|
||||
export abstract class RequestRequirement
|
||||
{
|
||||
|
|
@ -24,6 +25,6 @@ export abstract class RequestRequirement
|
|||
}
|
||||
|
||||
|
||||
abstract handle( request:FastifyRequest, reply:FastifyReply ):Promise<boolean>
|
||||
abstract handle( request:FastifyRequest, reply:FastifyReply ):Promise<Message[]>
|
||||
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import { MapList } from "../../../../browser/tools/MapList";
|
|||
import { DateHelper } from "../../../../browser/date/DateHelper";
|
||||
import { Duration } from "../../../../browser/date/Duration";
|
||||
import { DateMath } from "../../../../browser/date/DateMath";
|
||||
import { Message } from "../../../../browser/messages/Message";
|
||||
|
||||
|
||||
export class NotTooManyRequests extends RequestRequirement
|
||||
|
|
@ -17,7 +18,7 @@ export class NotTooManyRequests extends RequestRequirement
|
|||
_blockList = new Map<string,number>()
|
||||
_blockDuration = Duration.fromHours( 4 );
|
||||
|
||||
async handle( request:FastifyRequest, reply:FastifyReply ):Promise<boolean>
|
||||
async handle( request:FastifyRequest, reply:FastifyReply ):Promise<Message[]>
|
||||
{
|
||||
if ( this._blockList.has( request.ip ) )
|
||||
{
|
||||
|
|
@ -25,13 +26,18 @@ export class NotTooManyRequests extends RequestRequirement
|
|||
|
||||
if ( ! DateMath.isExpired( new Date( blockTime + this._blockDuration ) ) )
|
||||
{
|
||||
return Promise.resolve( false );
|
||||
return Promise.resolve( [ Message.Error( "User blocked" ) ] );
|
||||
}
|
||||
}
|
||||
|
||||
let valid = this.updateIP( request.ip );
|
||||
|
||||
return Promise.resolve( valid );
|
||||
if ( ! valid )
|
||||
{
|
||||
return Promise.resolve( [ Message.Error( "Too many requests" ) ] );
|
||||
}
|
||||
|
||||
return Promise.resolve( [] );
|
||||
}
|
||||
|
||||
async updateIP( ip:string ):Promise<boolean>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
|
||||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { RequestRequirement } from '../RequestRequirement';
|
||||
import { Message } from '../../../../browser/messages/Message';
|
||||
|
||||
export class UserHasPermission extends RequestRequirement
|
||||
{
|
||||
|
|
@ -12,12 +13,19 @@ export class UserHasPermission extends RequestRequirement
|
|||
this._permissionID = permissionID;
|
||||
}
|
||||
|
||||
async handle( request:FastifyRequest, reply:FastifyReply ):Promise<boolean>
|
||||
async handle( request:FastifyRequest, reply:FastifyReply ):Promise<Message[]>
|
||||
{
|
||||
let user = await this._handler.getUser();
|
||||
|
||||
let userDB = this._handler.userDB;
|
||||
|
||||
return userDB.hasPermission( user, this._permissionID );
|
||||
let hasPermission = await userDB.hasPermission( user, this._permissionID );
|
||||
|
||||
if ( ! hasPermission )
|
||||
{
|
||||
return Promise.resolve( [ Message.Error( "User has no permission for " + this._permissionID ) ] );
|
||||
}
|
||||
|
||||
return Promise.resolve( [] );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,19 @@
|
|||
|
||||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { RequestRequirement } from '../RequestRequirement';
|
||||
import { Message } from '../../../../browser/messages/Message';
|
||||
|
||||
export class UserIsLoggedIn extends RequestRequirement
|
||||
{
|
||||
|
||||
async handle( request:FastifyRequest, reply:FastifyReply ):Promise<boolean>
|
||||
async handle( request:FastifyRequest, reply:FastifyReply ):Promise<Message[]>
|
||||
{
|
||||
let requestBody = request.body;
|
||||
let tokenData = requestBody as { token:string };
|
||||
|
||||
if ( ! tokenData )
|
||||
{
|
||||
return Promise.resolve( false );
|
||||
return Promise.resolve( [ Message.Error( "No token data" )] );
|
||||
}
|
||||
|
||||
let tokenID = tokenData.token;
|
||||
|
|
@ -21,7 +22,7 @@ export class UserIsLoggedIn extends RequestRequirement
|
|||
|
||||
if ( ! session )
|
||||
{
|
||||
return Promise.resolve( false );
|
||||
return Promise.resolve( [ Message.Error( "No session for token:" + tokenID )] );
|
||||
}
|
||||
|
||||
let token = this._handler._ums.tokenDB._tokens.get( tokenID );
|
||||
|
|
@ -30,9 +31,9 @@ export class UserIsLoggedIn extends RequestRequirement
|
|||
|
||||
if ( ! isValid )
|
||||
{
|
||||
return Promise.resolve( false );
|
||||
return Promise.resolve( [ Message.Error( "Invalid token" + tokenID )] );
|
||||
}
|
||||
|
||||
return Promise.resolve( true );
|
||||
return Promise.resolve( [] );
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue