critical bugfix to user creation/updates

rename some class members
This commit is contained in:
Erik 2023-07-16 17:53:23 +03:00
parent 9e5fd57971
commit bc82da67a2
Signed by: Navy.gif
GPG Key ID: 2532FBBB61C65A68
10 changed files with 285 additions and 225 deletions

View File

@ -17,22 +17,22 @@ class Shard extends EventEmitter
{
// #_controller: Controller;
#_id: number;
#_filePath: string;
#_args: string[];
#_execArgv: string[];
#_env: { [key: string]: string };
#_respawn: boolean;
#_serverOptions: ServerOptions;
#id: number;
#filePath: string;
#args: string[];
#execArgv: string[];
#env: { [key: string]: string };
#respawn: boolean;
#serverOptions: ServerOptions;
#_ready: boolean;
#_process: ChildProcess | null;
#_fatal: boolean;
#ready: boolean;
#process: ChildProcess | null;
#fatal: boolean;
#_crashes: number[];
#_spawnedAt: number;
#_awaitingShutdown: (() => void) | null;
#_awaitingResponse: Map<string, (args: IPCMessage) => void>;
#crashes: number[];
#spawnedAt: number;
#awaitingShutdown: (() => void) | null;
#awaitingResponse: Map<string, (args: IPCMessage) => void>;
constructor (_controller: Controller, id: number, options: ShardOptions)
{
@ -41,56 +41,56 @@ class Shard extends EventEmitter
// this.#_controller = controller;
if (typeof id !== 'number' || isNaN(id))
throw new Error('Missing ID');
this.#_id = id;
this.#id = id;
if (!options?.path)
throw new Error('Missing path to file to fork');
this.#_filePath = options.path;
this.#_args = options.args || [];
this.#_execArgv = options.execArgv || [];
this.#_env = options.env || {};
this.#_respawn = options.respawn || false;
this.#_serverOptions = options.serverOptions || {} as ServerOptions;
this.#_serverOptions.dir = path.resolve(options.path, '..');
this.#filePath = options.path;
this.#args = options.args || [];
this.#execArgv = options.execArgv || [];
this.#env = options.env || {};
this.#respawn = options.respawn || false;
this.#serverOptions = options.serverOptions || {} as ServerOptions;
this.#serverOptions.dir = path.resolve(options.path, '..');
this.#_ready = false;
this.#_process = null;
this.#_fatal = false;
this.#ready = false;
this.#process = null;
this.#fatal = false;
// Keep track of crashes for preventing crash loops
this.#_crashes = [];
this.#crashes = [];
// Set in the spawn method
this.#_spawnedAt = Date.now(); // Gets re-set once actually spawned
this.#spawnedAt = Date.now(); // Gets re-set once actually spawned
this.#_awaitingShutdown = null;
this.#awaitingShutdown = null;
this.#_awaitingResponse = new Map();
this.#awaitingResponse = new Map();
}
get id ()
{
return this.#_id;
return this.#id;
}
get fatal ()
{
return this.#_fatal;
return this.#fatal;
}
get process ()
{
return this.#_process;
return this.#process;
}
get ready ()
{
return this.#_ready;
return this.#ready;
}
get spawnedAt ()
{
return this.#_spawnedAt;
return this.#spawnedAt;
}
async spawn (waitForReady = false)
@ -101,24 +101,24 @@ class Shard extends EventEmitter
if (this.process)
throw new Error(`[shard-${this.id}] A process for this shard already exists!`);
this.#_process = fork(this.#_filePath, this.#_args, {
this.#process = fork(this.#filePath, this.#args, {
env: {
...this.#_env,
...this.#env,
SHARD_ID: this.id.toString()
},
execArgv: this.#_execArgv
execArgv: this.#execArgv
})
.on('message', this._handleMessage.bind(this))
.on('exit', this._handleExit.bind(this))
.on('disconnect', this._handleDisconnect.bind(this)); // Don't know if this is going to help, but monitoring whether this gets called whenever a process on its own closes the IPC channel
this.#_process.once('spawn', () =>
this.#process.once('spawn', () =>
{
this.emit('spawn');
if (!this.#_process)
if (!this.#process)
throw new Error('Shut up TS');
this.#_process.send({ _start: this.#_serverOptions });
this.#_spawnedAt = Date.now();
this.#process.send({ _start: this.#serverOptions });
this.#spawnedAt = Date.now();
});
if (!waitForReady)
return;
@ -155,19 +155,19 @@ class Shard extends EventEmitter
return new Promise<void>((resolve) =>
{
// Clear out all other exit listeners so they don't accidentally start the process up again
if (!this.#_process)
if (!this.#process)
return resolve();
this.#_process.removeAllListeners('exit');
this.#process.removeAllListeners('exit');
// Set timeout for force kill
const to = setTimeout(() =>
{
if (!this.#_process)
if (!this.#process)
return resolve();
this.#_process.kill();
this.#process.kill();
resolve();
}, KillTO);
// Gracefully handle exit
this.#_process.once('exit', (code, signal) =>
this.#process.once('exit', (code, signal) =>
{
clearTimeout(to);
this._handleExit(code, signal, false);
@ -179,7 +179,7 @@ class Shard extends EventEmitter
clearTimeout(to);
});
this.#_process.send({ _shutdown: true });
this.#process.send({ _shutdown: true });
});
}
this._handleExit(null, null, false);
@ -188,7 +188,7 @@ class Shard extends EventEmitter
send (message: IPCMessage, expectResponse = false): Promise<IPCMessage | void>
{
if (!this.ready || !this.#_process)
if (!this.ready || !this.#process)
return Promise.reject(new Error(`[shard-${this.id}] Cannot send message to dead shard.`));
return new Promise<IPCMessage | void>((resolve, reject) =>
@ -198,14 +198,14 @@ class Shard extends EventEmitter
{
message._id = Util.randomUUID();
const to = setTimeout(reject, 10_000, [ new Error('Message timeout') ]);
this.#_awaitingResponse.set(message._id, (args: IPCMessage) =>
this.#awaitingResponse.set(message._id, (args: IPCMessage) =>
{
clearTimeout(to);
resolve(args);
});
}
this.#_process?.send(message, err =>
this.#process?.send(message, err =>
{
if (err)
return reject(err);
@ -219,10 +219,10 @@ class Shard extends EventEmitter
awaitShutdown ()
{
this.#_respawn = false;
this.#respawn = false;
return new Promise<void>((resolve) =>
{
this.#_awaitingShutdown = resolve;
this.#awaitingShutdown = resolve;
});
}
@ -232,7 +232,7 @@ class Shard extends EventEmitter
{
if (message._ready)
{
this.#_ready = true;
this.#ready = true;
this.emit('ready');
return;
}
@ -240,27 +240,27 @@ class Shard extends EventEmitter
{
const TO = setTimeout(() =>
{
this.#_process?.kill('SIGKILL');
this.#process?.kill('SIGKILL');
}, KillTO);
this.#_process?.once('exit', () =>
this.#process?.once('exit', () =>
{
clearTimeout(TO);
});
this.#_ready = false;
this.#ready = false;
this.emit('shutdown');
return;
}
else if (message._fatal)
{
this.#_process?.removeAllListeners();
this.#_ready = false;
this.#_fatal = true;
this.#process?.removeAllListeners();
this.#ready = false;
this.#fatal = true;
this._handleExit(null, null, false);
return this.emit('fatal', message);
}
else if (message._id)
{
const promise = this.#_awaitingResponse.get(message._id);
const promise = this.#awaitingResponse.get(message._id);
if (promise)
return promise(message);
}
@ -275,28 +275,28 @@ class Shard extends EventEmitter
this.emit('disconnect');
}
_handleExit (code: number | null, _signal: string | null, respawn = this.#_respawn)
_handleExit (code: number | null, _signal: string | null, respawn = this.#respawn)
{
if (this.process)
this.process.removeAllListeners();
this.emit('death');
if (this.#_awaitingShutdown)
this.#_awaitingShutdown();
if (this.#awaitingShutdown)
this.#awaitingShutdown();
if (code !== 0)
{
this.#_crashes.push(Date.now() - this.spawnedAt);
this.#crashes.push(Date.now() - this.spawnedAt);
this.emit('warn', 'Shard exited with non-zero exit code');
}
this.#_ready = false;
this.#_process = null;
this.#ready = false;
this.#process = null;
const len = this.#_crashes.length;
const len = this.#crashes.length;
if (len > 2)
{
const last3 = this.#_crashes.slice(len - 3);
const last3 = this.#crashes.slice(len - 3);
const sum = last3.reduce((s, val) =>
{
s += val;

View File

@ -412,9 +412,9 @@ class Server extends EventEmitter
try
{
const user = await this.users.createUser(name, pass);
process.send({ _id: msg._id, success: true });
if (admin)
await user.updatePermissions({ administrator: { default: 10 } });
process.send({ _id: msg._id, success: true });
}
catch (err)
{

View File

@ -399,7 +399,10 @@ class UserDatabase implements UserDatabaseInterface
name
});
await user.setPassword(password);
await this.#db.insertOne(this.#_userCollection, user.jsonPrivate);
const json = user.jsonPrivate as {_id?: ObjectId, id?: string};
json._id = new ObjectId(json.id);
delete json.id;
await this.#db.insertOne(this.#_userCollection, json);
// await this.updateUser(user);
if (!this.#disableCache)
this.#cache.set(user.id, user);
@ -411,8 +414,10 @@ class UserDatabase implements UserDatabaseInterface
async createApplication (data: ApplicationData)
{
const app = this._createApp(data);
// await this.updateApplication(app);
await this.#db.insertOne(this.#_appCollection, app.jsonPrivate);
const json = app.jsonPrivate as {_id?: ObjectId, id?: string};
json._id = new ObjectId(json.id);
delete json.id;
await this.#db.insertOne(this.#_appCollection, json);
if (!this.#disableCache)
this.#cache.set(app.id, app);
this.#amount.applications++;
@ -510,11 +515,11 @@ class UserDatabase implements UserDatabaseInterface
* @return {*} {Promise<T>}
* @memberof UserDatabase
*/
async _updateEntity<T> (entity: T, collection: string): Promise<T>
async _updateEntity<T extends Entity> (entity: T, collection: string): Promise<T>
{
if (!(entity instanceof Entity))
throw new Error('Cannot save a non-entity instance');
const json = entity.json as {id?: string};
const json = entity.jsonPrivate as { id?: string };
delete json.id;
await this.#db.updateOne(collection, { _id: new ObjectId(entity.id) }, json);
return entity;

View File

@ -9,9 +9,9 @@ class AbstractUser extends Entity
static override ProtectedFields: string[] = [ ];
#_icon: string | null;
#_roles: Role[];
#_temporary: boolean;
#icon: string | null;
#roles: Role[];
#temporary: boolean;
constructor (server: Server, { icon, roles = [], ...opts }: AbstractUserData)
{
@ -19,9 +19,9 @@ class AbstractUser extends Entity
if (this.constructor === AbstractUser)
throw new Error('This class cannot be instantiated, only derived');
this.#_icon = icon || null;
this.#_roles = roles as Role[];
this.#_temporary = opts.temporary || false;
this.#icon = icon || null;
this.#roles = roles as Role[];
this.#temporary = opts.temporary || false;
}
@ -32,12 +32,12 @@ class AbstractUser extends Entity
get temporary ()
{
return this.#_temporary;
return this.#temporary;
}
set temporary (val: boolean)
{
this.#_temporary = val;
this.#temporary = val;
}
fetchRateLimits ()
@ -47,12 +47,12 @@ class AbstractUser extends Entity
get roles ()
{
return this.#_roles;
return this.#roles;
}
setIcon (icon: string)
{
this.#_icon = icon;
this.#icon = icon;
}
/**
@ -65,7 +65,7 @@ class AbstractUser extends Entity
return {
...super.jsonPrivate,
icon: this.icon,
roles: this.#_roles.map(role => role.id)
roles: this.#roles.map(role => role.id)
};
}
@ -94,7 +94,7 @@ class AbstractUser extends Entity
get icon ()
{
return this.#_icon;
return this.#icon;
}
}

View File

@ -12,21 +12,21 @@ type SubpathDefinition = [string, string, HandlerFunction | null, (MiddlewareDef
abstract class Endpoint
{
#_server: Server;
#_path: string;
#_name: string;
#server: Server;
#path: string;
#name: string;
_subpaths: SubpathDefinition;
subpaths: SubpathDefinition;
#_subpaths: SubpathDefinition;
#subpaths: SubpathDefinition;
_middleware: MiddlewareDefition;
middleware: MiddlewareDefition;
#_middleware: MiddlewareDefition;
#middleware: MiddlewareDefition;
methods: EndpointDefinition;
rateLimiter: RateLimiter;
logger: LoggerClient;
#methods: EndpointDefinition;
#rateLimiter: RateLimiter;
#logger: LoggerClient;
loadOrder: number;
#loadOrder: number;
#auth: MiddlewareFunction | null;
constructor (server: Server, { path, name, loadOrder = 5, auth = null }: EndpointOptions)
@ -34,45 +34,45 @@ abstract class Endpoint
if (!server)
Util.fatal(new Error('Missing server object in endpoint'));
this.#_server = server;
this.#server = server;
if (!path)
Util.fatal(new Error('Missing path in endpoint'));
if (!path.startsWith('/'))
path = `/${path}`;
this.#_path = path;
this.#path = path;
if (name)
this.#_name = name;
this.#name = name;
else
this.#_name = path;
this.#name = path;
if (auth === true)
this.#auth = server.auth.authenticate;
else
this.#auth = auth;
this.subpaths = [];
this.#subpaths = [];
// Subpaths that should exist on *all* endpoints, the subpaths property can be overwritten, so storing these separately to ensure they exist
this._subpaths = [
this.#_subpaths = [
[ 'post', '/debug', this.toggleDebug.bind(this), [ server.authenticator.createAuthoriser('developer') ]]
];
// Same as above but for inheriting intermediary endpoint classes
// e.g. the API endpoint class adds a ratelimiter to this
// doing like this to ensure the actual endpoint classes don't overwrite it when defining middleware
this._middleware = [ ];
this.middleware = [];
this.#_middleware = [ ];
this.#middleware = [];
this.methods = [];
this.#methods = [];
this.rateLimiter = server.rateLimiter;
this.logger = server.createLogger(this);
this.#rateLimiter = server.rateLimiter;
this.#logger = server.createLogger(this);
// Used to sort the endpoints from smallest to highest before initialisation
// Useful when needing to have certain endpoints register before or after some other endpoint
// E.g. 404 pages should be initialised last by having a loadOrder of 10
this.loadOrder = loadOrder;
this.#loadOrder = loadOrder;
}
@ -80,7 +80,7 @@ abstract class Endpoint
{
// eslint-disable-next-line prefer-const
for (let [ method, cb, mw = [] ] of this.methods)
for (let [ method, cb, mw = [] ] of this.#methods)
{
if (typeof method !== 'string')
throw new Error(`Invalid method parameter type in Endpoint ${this.name} major path`);
@ -92,19 +92,19 @@ abstract class Endpoint
const middleware = [];
if (this.#auth)
middleware.push(this.#auth);
middleware.push(...this._middleware, ...this.middleware, ...mw);
middleware.push(...this.#_middleware, ...this.#middleware, ...mw);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.server.app[method](this.path, ...middleware, cb);
}
this.subpaths = [ ...this._subpaths, ...this.subpaths ];
this.#subpaths = [ ...this.#_subpaths, ...this.#subpaths ];
// eslint-disable-next-line prefer-const
for (let [ method, sub, cb, mw = [] ] of this.subpaths)
for (let [ method, sub, cb, mw = [] ] of this.#subpaths)
{
if (typeof method !== 'string')
throw new Error(`Invalid method parameter type in Endpoint ${this.name} subpath ${sub}`);
if (!this.middleware.length && !mw.length && !cb)
if (!this.#middleware.length && !mw.length && !cb)
throw new Error('Cannot have endpoint with no handler and no middleware, expecting at least one to be defined');
if (typeof mw === 'function')
mw = [ mw ];
@ -114,7 +114,7 @@ abstract class Endpoint
const middleware = [];
if (this.#auth)
middleware.push(this.#auth);
middleware.push(...this._middleware, ...this.middleware, ...mw);
middleware.push(...this.#_middleware, ...this.#middleware, ...mw);
const args = [ this.path + sub, ...middleware ];
if (cb)
@ -136,26 +136,71 @@ abstract class Endpoint
const { body } = req;
if (typeof body.value !== 'string')
return void res.status(400).send(`Invalid value, must be one of ${this.logger.logLevels}`);
this.logger.info(`Setting debug mode on endpoint ${this.name} to ${body.value}`);
this.logger.setLogLevel(body.value);
return void res.status(400).send(`Invalid value, must be one of ${this.#logger.logLevels}`);
this.#logger.info(`Setting debug mode on endpoint ${this.name} to ${body.value}`);
this.#logger.setLogLevel(body.value);
res.status(200).send(body.value);
}
protected get logger ()
{
return this.#logger;
}
protected get rateLimiter ()
{
return this.#rateLimiter;
}
get methods ()
{
return this.#methods;
}
protected set methods (methods)
{
this.#methods = methods;
}
protected get middleware ()
{
return this.#middleware;
}
protected set middleware (mw)
{
this.#middleware = mw;
}
get subpaths ()
{
return this.#subpaths;
}
protected set subpaths (subs)
{
this.#subpaths = subs;
}
get server ()
{
return this.#_server;
return this.#server;
}
get path ()
{
return this.#_path;
return this.#path;
}
get name ()
{
return this.#_name;
return this.#name;
}
get loadOrder ()
{
return this.#loadOrder;
}
get resolveable ()

View File

@ -10,16 +10,16 @@ class Entity
static ProtectedFields = [ '_id' ];
#_id: string;
#_name: string;
#_disabled: boolean;
#_permissions: Permissions;
#_cachedTimestamp: number;
#_createdTimestamp: number;
#_note: string | null;
#id: string;
#name: string;
#disabled: boolean;
#permissions: Permissions;
#cachedTimestamp: number;
#createdTimestamp: number;
#note: string | null;
_db: UserDatabaseInterface;
_server: Server;
#db: UserDatabaseInterface;
#server: Server;
constructor (server: Server, { note, name, disabled, id, permissions, createdTimestamp }: EntityData)
{
@ -38,18 +38,28 @@ class Entity
if (!name)
throw new Error('Entities require a name');
this._server = server;
this._db = server.users;
this.#_id = id;
this.#_name = name;
this.#_disabled = disabled ?? false;
this.#_permissions = PermissionManager.merge(permissions || {}, PermissionManager.DefaultPermissions);
this.#_createdTimestamp = createdTimestamp ?? Date.now();
this.#_cachedTimestamp = Date.now();
this.#_note = note ?? null;
this.#server = server;
this.#db = server.users;
this.#id = id;
this.#name = name;
this.#disabled = disabled ?? false;
this.#permissions = PermissionManager.merge(permissions || {}, PermissionManager.DefaultPermissions);
this.#createdTimestamp = createdTimestamp ?? Date.now();
this.#cachedTimestamp = Date.now();
this.#note = note ?? null;
}
protected get db ()
{
return this.#db;
}
protected get server ()
{
return this.#server;
}
save ()
{
throw new Error('Expecting this function to be implemented in an inheriting class');
@ -63,7 +73,7 @@ class Entity
updatePermissions (perms: Permissions)
{
PermissionManager.validatePermissions(perms);
this.#_permissions = PermissionManager.merge(this.#_permissions, perms, true);
this.#permissions = PermissionManager.merge(this.#permissions, perms, true);
return this.save();
}
@ -101,52 +111,52 @@ class Entity
get id ()
{
return this.#_id;
return this.#id;
}
get name ()
{
return this.#_name;
return this.#name;
}
set name (str: string)
{
this.#_name = str;
this.#name = str;
}
get disabled ()
{
return this.#_disabled;
return this.#disabled;
}
set disabled (val: boolean)
{
this.#_disabled = val;
this.#disabled = val;
}
get permissions ()
{
return Object.freeze({ ...this.#_permissions });
return Object.freeze({ ...this.#permissions });
}
get note ()
{
return this.#_note;
return this.#note;
}
get createdAt ()
{
return new Date(this.#_createdTimestamp);
return new Date(this.#createdTimestamp);
}
get createdTimestamp ()
{
return this.#_createdTimestamp;
return this.#createdTimestamp;
}
get cachedTimestamp ()
{
return this.#_cachedTimestamp;
return this.#cachedTimestamp;
}
}

View File

@ -13,15 +13,15 @@ class Flag
#manager: FlagManager;
#_id: string;
#_name: string;
#_hierarchy: string;
#id: string;
#name: string;
#hierarchy: string;
#_env: FlagEnv;
#_consumer: FlagConsumer;
#env: FlagEnv;
#consumer: FlagConsumer;
#_value: FlagType;
#_type: string;
#value: FlagType;
#type: string;
constructor (manager: FlagManager, data: FlagData)
{
@ -41,18 +41,18 @@ class Flag
if (!('value' in data))
throw new Error('Missing value');
this.#_id = data._id;
this.#_env = data.env;
this.#_consumer = data.consumer;
this.#_name = data.name;
this.#_value = data.value;
this.#_hierarchy = data.hierarchy || '';
this.#_type = Flag.resolveType(data.value);
this.#id = data._id;
this.#env = data.env;
this.#consumer = data.consumer;
this.#name = data.name;
this.#value = data.value;
this.#hierarchy = data.hierarchy || '';
this.#type = Flag.resolveType(data.value);
}
get<T> ()
{
return this.#_value as T;
return this.#value as T;
}
save (): Promise<void>
@ -62,64 +62,64 @@ class Flag
get name ()
{
return this.#_name;
return this.#name;
}
set name (val)
{
this.#_name = val;
this.#name = val;
}
get id ()
{
return this.#_id;
return this.#id;
}
get type ()
{
return this.#_type;
return this.#type;
}
get value ()
{
return this.#_value;
return this.#value;
}
set value (val)
{
if (typeof val !== typeof this.value)
throw new Error('Value type mismatch');
this.#_value = val;
this.#value = val;
}
get env ()
{
return this.#_env;
return this.#env;
}
set env (val)
{
this.#_env = val;
this.#env = val;
}
get consumer ()
{
return this.#_consumer;
return this.#consumer;
}
set consumer (val)
{
this.#_consumer = val;
this.#consumer = val;
}
get hierarchy ()
{
return this.#_hierarchy;
return this.#hierarchy;
}
set hierarchy (val)
{
this.#_hierarchy = val;
this.#hierarchy = val;
}
get json (): FlagData

View File

@ -8,45 +8,45 @@ class Role extends Entity
static override ProtectedFields = [ ];
#_rateLimits: RateLimits;
#_position: number;
#rateLimits: RateLimits;
#position: number;
#_rateLimiter: RateLimiter;
#rateLimiter: RateLimiter;
constructor (server: Server, { rateLimits = {}, position, ...data }: RoleData)
{
super(server, data);
this.#_rateLimiter = server.rateLimiter;
this.#_rateLimits = this.#_rateLimiter.validateLimits(rateLimits, { addMissing: true, deleteInvalid: true });
this.#rateLimiter = server.rateLimiter;
this.#rateLimits = this.#rateLimiter.validateLimits(rateLimits, { addMissing: true, deleteInvalid: true });
if (position === null)
throw new Error('Must supply position');
this.#_position = position;
this.#position = position;
}
override save ()
{
return this._db.updateRole(this);
return this.db.updateRole(this);
}
updateRateLimits (limits: RateLimits)
{
limits = this.#_rateLimiter.validateLimits(limits);
limits = this.#rateLimiter.validateLimits(limits);
const entries = Object.entries(limits);
for (const [ key, val ] of entries)
{
this.#_rateLimits[key] = val;
this.#rateLimits[key] = val;
}
return this.save();
}
get rateLimits ()
{
return this.#_rateLimits;
return this.#rateLimits;
}
get position ()
{
return this.#_position;
return this.#position;
}
override get jsonPrivate ()
@ -54,8 +54,8 @@ class Role extends Entity
const json = super.jsonPrivate;
return {
...json,
rateLimits: this.#_rateLimits,
position: this.#_position
rateLimits: this.#rateLimits,
position: this.#position
};
}

View File

@ -14,12 +14,12 @@ class User extends AbstractUser
{
#passwordHash: string | null;
#_otpSecret: string | null;
#_mfa: boolean;
#otpSecret: string | null;
#mfa: boolean;
#_displayName: string | null;
#_externalProfiles: {[key: string]: ExternalProfile};
#_applications: string[];
#displayName: string | null;
#externalProfiles: {[key: string]: ExternalProfile};
#applications: string[];
constructor (server: Server, data: UserData)
{
@ -28,30 +28,30 @@ class User extends AbstractUser
AbstractUser.ProtectedFields.push('otpSecret', 'password');
this.#_applications = data.applications || [];
this.#_externalProfiles = data.externalProfiles || {};
this.#applications = data.applications || [];
this.#externalProfiles = data.externalProfiles || {};
/** @private */
this.#passwordHash = data.password || null;
this.#_otpSecret = data.otpSecret || null;
this.#_mfa = data.twoFactor || false;
this.#_displayName = data.displayName || null;
this.#otpSecret = data.otpSecret || null;
this.#mfa = data.twoFactor || false;
this.#displayName = data.displayName || null;
}
override save ()
{
return this._db.updateUser(this);
return this.db.updateUser(this);
}
get displayName ()
{
return this.#_displayName || this.username;
return this.#displayName || this.username;
}
set displayName (str)
{
this.#_displayName = str;
this.#displayName = str;
}
get username ()
@ -76,37 +76,37 @@ class User extends AbstractUser
override get twoFactor ()
{
return this.#_mfa;
return this.#mfa;
}
override set twoFactor (val: boolean)
{
this.#_mfa = val;
this.#mfa = val;
}
get mfa ()
{
return this.#_mfa;
return this.#mfa;
}
set mfa (val: boolean)
{
this.#_mfa = val;
this.#mfa = val;
}
get externalProfiles ()
{
return Object.freeze({ ...this.#_externalProfiles });
return Object.freeze({ ...this.#externalProfiles });
}
get applications ()
{
return this.#_applications;
return this.#applications;
}
get otpSecret ()
{
return this.#_otpSecret;
return this.#otpSecret;
}
async setPassword (passwd: string, save = false)
@ -119,7 +119,7 @@ class User extends AbstractUser
setOtpSecret (secret: string)
{
this.#_otpSecret = secret;
this.#otpSecret = secret;
}
async authenticate (passwd: string)
@ -145,7 +145,7 @@ class User extends AbstractUser
addExternalProfile (platform: string, profile: ExternalProfile)
{
profile.provider = platform;
this.#_externalProfiles[platform] = profile;
this.#externalProfiles[platform] = profile;
}
hasExternalProfile (name: string)
@ -160,9 +160,9 @@ class User extends AbstractUser
displayName: this.displayName,
externalProfiles: this.externalProfiles,
password: this.#passwordHash,
otpSecret: this.#_otpSecret,
otpSecret: this.#otpSecret,
twoFactor: this.twoFactor,
applications: this.#_applications,
applications: this.#applications,
};
}

View File

@ -9,9 +9,9 @@ import { AbstractUser } from '../interfaces/index.js';
class UserApplication extends AbstractUser
{
#_token: string;
#_user: string;
#_description: string | null;
#token: string;
#user: string;
#description: string | null;
constructor (server: Server, options: ApplicationData)
{
@ -20,35 +20,35 @@ class UserApplication extends AbstractUser
if (typeof options.token !== 'string')
throw new Error('Missing token for appliaction');
this.#_token = options.token;
this.#token = options.token;
if (options.ownerId instanceof ObjectId)
options.ownerId = options.ownerId.toString();
if (typeof options.ownerId !== 'string')
throw new Error('Missing ownerId (owner) for application');
this.#_user = options.ownerId;
this.#_description = options.description || null;
this.#user = options.ownerId;
this.#description = options.description || null;
}
override save ()
{
return this._db.updateApplication(this);
return this.db.updateApplication(this);
}
get ownerId ()
{
return this.#_user;
return this.#user;
}
get description ()
{
return this.#_description;
return this.#description;
}
get token ()
{
return this.#_token;
return this.#token;
}
override get jsonPrivate ()
@ -56,7 +56,7 @@ class UserApplication extends AbstractUser
return {
...super.jsonPrivate,
token: this.token,
ownerId: new ObjectId(this.#_user),
ownerId: new ObjectId(this.#user),
description: this.description,
};
}