173 lines
4.2 KiB
JavaScript
173 lines
4.2 KiB
JavaScript
import { BASIC_TYPES } from '@og/binary-struct';
|
|
import { UUID } from '@og/uuid';
|
|
import { Model, BaseModel } from '../../kv/index.mjs';
|
|
import * as config from '../../config.mjs';
|
|
import { hashPassword, isWeakPassword } from './password.mjs';
|
|
import { timingSafeEqual } from 'node:crypto';
|
|
import { InvalidArgumentError } from '@og/error-utils';
|
|
import { isValidHandle } from './handle.mjs';
|
|
|
|
export const ACCOUNT_TYPE = {
|
|
banned: 0x00,
|
|
normal: 0x01,
|
|
admin: 0x02,
|
|
robot: 0x03,
|
|
temporary: 0x04,
|
|
superuser: 0xff,
|
|
};
|
|
|
|
export const ACCOUNT_UUID_NS = 'aff7791d-4b71-4187-9788-13fa0c7fb51e';
|
|
|
|
function calcAccountUUID(handle) {
|
|
return UUID.v5('account:' + handle, ACCOUNT_UUID_NS);
|
|
}
|
|
|
|
/**
|
|
* A Model repersenting an user account.
|
|
* @class
|
|
* @extends {BaseModel}
|
|
*/
|
|
export class Account extends Model('Account', config.kv_instance) {
|
|
/** @type {UUID} */
|
|
uid;
|
|
|
|
/** @type {number} */
|
|
type;
|
|
|
|
/** @type {string} */
|
|
handle;
|
|
|
|
/** @type {string} */
|
|
name;
|
|
|
|
/** @type {Buffer} */
|
|
passwd;
|
|
|
|
/** @type {Buffer} */
|
|
totp_key = Buffer.alloc(20, 0);
|
|
|
|
/** @type {Date} */
|
|
created_at = new Date();
|
|
|
|
/** @type {number} */
|
|
rating = 0;
|
|
|
|
/** @type {Map} */
|
|
preference = new Map();
|
|
|
|
/** @type {UUID[]} */
|
|
replays = [];
|
|
|
|
/**
|
|
* Getter of the primary key of Account.
|
|
* @returns {string|null} The primary key value or null if not applicable.
|
|
*/
|
|
get pk() {
|
|
return this.uid?.toString();
|
|
}
|
|
|
|
/**
|
|
* Set a new handle for the account.
|
|
* @param {string} handle - new handle.
|
|
*/
|
|
setHandle(handle) {
|
|
if (!isValidHandle(handle)) {
|
|
throw InvalidArgumentError('handle', 'handle is unacceptable');
|
|
}
|
|
this.handle = handle;
|
|
this.uid = calcAccountUUID(handle);
|
|
}
|
|
|
|
/**
|
|
* Set a new password for the account.
|
|
* @param {string} passwd - new password.
|
|
*/
|
|
setPassword(passwd) {
|
|
if (isWeakPassword(passwd)) {
|
|
throw InvalidArgumentError('passwd', 'password is too weak.');
|
|
}
|
|
this.passwd = hashPassword(passwd);
|
|
}
|
|
|
|
/**
|
|
* Clears the password of the account.
|
|
*/
|
|
clearPassword() {
|
|
this.type = ACCOUNT_TYPE.temporary;
|
|
this.passwd = hashPassword('');
|
|
}
|
|
|
|
/**
|
|
* Checks if the given password is correct.
|
|
* @param {string} passwd - password.
|
|
* @returns {boolean} - true if the given value is the password of the account.
|
|
*/
|
|
checkPassword(input) {
|
|
const ihash = hashPassword(input);
|
|
return timingSafeEqual(ihash, this.passwd);
|
|
}
|
|
|
|
/**
|
|
* Checks if the account can be logged in.
|
|
* @returns {boolean} - true if the account can be logged in.
|
|
*/
|
|
canLogin() {
|
|
return this.type != ACCOUNT_TYPE.banned;
|
|
}
|
|
|
|
/**
|
|
* Asynchronously loads a Account instance based on its handle.
|
|
* @param {string} handle - the handle of the account to load.
|
|
* @returns {Promise<?Account>} - The loaded account instance or null if not found.
|
|
* @async
|
|
*/
|
|
static loadByHandle(handle) {
|
|
return this.load(calcAccountUUID(handle).toString());
|
|
}
|
|
|
|
/**
|
|
* Creates an new account.
|
|
* @param {{handle: string, name: string, type: number, plaintext_password: string | null}}
|
|
* @returns {Account} Created account.
|
|
*/
|
|
static create({ handle, name, type, plaintext_password }) {
|
|
if (handle == null) {
|
|
throw InvalidArgumentError('handle', 'a handle is required');
|
|
}
|
|
|
|
name = name?.toString() ?? handle;
|
|
|
|
if (name.length < 3 || name.length > 32) {
|
|
throw InvalidArgumentError('name', 'name is too long or too short');
|
|
}
|
|
|
|
if (type != null && Object.values(ACCOUNT_TYPE).includes(type)) {
|
|
throw InvalidArgumentError('type', 'unknown type');
|
|
}
|
|
|
|
const res = new this();
|
|
res.setHandle(handle);
|
|
res.name = name;
|
|
res.type = type ?? ACCOUNT_TYPE.normal;
|
|
if (plaintext_password != null) {
|
|
res.setPassword(plaintext_password);
|
|
} else {
|
|
res.clearPassword();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static typedef = [
|
|
{ field: 'uid', type: BASIC_TYPES.uuid },
|
|
{ field: 'type', type: BASIC_TYPES.u8 },
|
|
{ field: 'handle', type: BASIC_TYPES.str },
|
|
{ field: 'name', type: BASIC_TYPES.str },
|
|
{ field: 'passwd', type: BASIC_TYPES.raw(32) },
|
|
{ field: 'totp_key', type: BASIC_TYPES.raw(20) },
|
|
{ field: 'created_at', type: BASIC_TYPES.DateTime },
|
|
{ field: 'rating', type: BASIC_TYPES.i32 },
|
|
{ field: 'preference', type: BASIC_TYPES.StringMap },
|
|
{ field: 'replays', type: BASIC_TYPES.array(BASIC_TYPES.uuid) },
|
|
];
|
|
};
|