Files
cloudron-box/src/apppasswords.js

90 lines
3.2 KiB
JavaScript
Raw Normal View History

2021-06-25 22:11:17 -07:00
'use strict';
exports = module.exports = {
get,
add,
list,
del,
removePrivateFields
};
const assert = require('node:assert'),
2021-06-25 22:11:17 -07:00
BoxError = require('./boxerror.js'),
crypto = require('node:crypto'),
2021-06-25 22:11:17 -07:00
database = require('./database.js'),
hat = require('./hat.js'),
safe = require('safetydance'),
_ = require('./underscore.js');
2021-06-25 22:11:17 -07:00
2026-02-12 12:58:50 +01:00
const APP_PASSWORD_FIELDS = [ 'id', 'name', 'userId', 'identifier', 'hashedPassword', 'creationTime', 'expiresAt' ].join(',');
2021-06-25 22:11:17 -07:00
function validateAppPasswordName(name) {
assert.strictEqual(typeof name, 'string');
if (name.length < 1) return new BoxError(BoxError.BAD_FIELD, 'name must be atleast 1 char');
if (name.length >= 200) return new BoxError(BoxError.BAD_FIELD, 'name too long');
return null;
}
function removePrivateFields(appPassword) {
2026-02-12 12:58:50 +01:00
return _.pick(appPassword, ['id', 'name', 'userId', 'identifier', 'creationTime', 'expiresAt']);
2021-06-25 22:11:17 -07:00
}
async function get(id) {
assert.strictEqual(typeof id, 'string');
const result = await database.query('SELECT ' + APP_PASSWORD_FIELDS + ' FROM appPasswords WHERE id = ?', [ id ]);
if (result.length === 0) return null;
return result[0];
}
2026-02-12 12:58:50 +01:00
async function add(userId, identifier, name, expiresAt) {
2021-06-25 22:11:17 -07:00
assert.strictEqual(typeof userId, 'string');
assert.strictEqual(typeof identifier, 'string');
assert.strictEqual(typeof name, 'string');
2026-02-12 12:58:50 +01:00
assert(expiresAt === null || typeof expiresAt === 'string');
2021-06-25 22:11:17 -07:00
let error = validateAppPasswordName(name);
if (error) throw error;
if (identifier.length < 1) throw new BoxError(BoxError.BAD_FIELD, 'identifier must be atleast 1 char');
const password = hat(16 * 4);
const hashedPassword = crypto.createHash('sha256').update(password).digest('base64');
const appPassword = {
id: 'uid-' + crypto.randomUUID(),
2021-06-25 22:11:17 -07:00
name,
userId,
identifier,
password,
2026-02-12 12:58:50 +01:00
hashedPassword,
expiresAt
2021-06-25 22:11:17 -07:00
};
2026-02-12 12:58:50 +01:00
const query = 'INSERT INTO appPasswords (id, userId, identifier, name, hashedPassword, expiresAt) VALUES (?, ?, ?, ?, ?, ?)';
const args = [ appPassword.id, appPassword.userId, appPassword.identifier, appPassword.name, appPassword.hashedPassword, appPassword.expiresAt ? new Date(appPassword.expiresAt) : null ];
2021-06-25 22:11:17 -07:00
[error] = await safe(database.query(query, args));
2025-09-29 11:55:15 +02:00
if (error && error.sqlCode === 'ER_DUP_ENTRY' && error.sqlMessage.indexOf('appPasswords_name_userId_identifier') !== -1) throw new BoxError(BoxError.ALREADY_EXISTS, 'name/app combination already exists');
if (error && error.sqlCode === 'ER_NO_REFERENCED_ROW_2' && error.sqlMessage.indexOf('userId')) throw new BoxError(BoxError.NOT_FOUND, 'user not found');
2021-06-25 22:11:17 -07:00
if (error) throw error;
return { id: appPassword.id, password: appPassword.password };
}
async function list(userId) {
assert.strictEqual(typeof userId, 'string');
return await database.query('SELECT ' + APP_PASSWORD_FIELDS + ' FROM appPasswords WHERE userId = ?', [ userId ]);
}
async function del(id) {
assert.strictEqual(typeof id, 'string');
const result = await database.query('DELETE FROM appPasswords WHERE id = ?', [ id ]);
if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'password not found');
}