+ this.logger.info(_scope, 'finished', { ctx });
+ }
+
+
+ /**
+ * Page for modifying credentials and OTP.
+ * @param {http.ServerResponse} res
+ * @param {Object} ctx
+ * @param {AppTemplateCallback} appCb
+ */
+ async getAdminSettings(res, ctx, appCb) {
+ const _scope = _fileScope('getAdminSettings');
+ this.logger.debug(_scope, 'called', { ctx });
+
+ try {
+ await this.db.context(async (dbCtx) => {
+ const authData = await this.db.authenticationGet(dbCtx, ctx.authenticationId);
+ if (!authData) {
+ ctx.errors.push('Sorry, you do not seem to exist! <pre>¯\\_(ツ)_/¯</pre> Cannot do anything useful here!');
+ return;
+ }
+ ctx.otpKey = authData.otpKey;
+ }); // dbCtx
+ } catch (e) {
+ this.logger.error(_scope, 'failed', { ctx, error: e });
+ ctx.errors.push('An error was encountered. Sorry that is not very helpful.');
+ }
+
+ res.end(Template.SettingsHTML(ctx, this.options, appCb));
+ this.logger.info(_scope, 'finished', { ctx });
+ }
+
+
+ /**
+ * Page for modifying credentials and OTP.
+ * @param {http.ServerResponse} res
+ * @param {Object} ctx
+ * @param {Object[]=} appNavLinks
+ * @param {AppTemplateCallback} appCb
+ */
+ async postAdminSettings(res, ctx, appCb) {
+ const _scope = _fileScope('postAdminSettings');
+ this.logger.debug(_scope, 'called', { ctx });
+
+ try {
+ await this.db.context(async (dbCtx) => {
+ const authData = await this.db.authenticationGet(dbCtx, ctx.authenticationId);
+ if (!authData) {
+ ctx.errors.push('Sorry, you do not seem to exist! <pre>¯\\_(ツ)_/¯</pre> Cannot do anything useful here!');
+ return;
+ }
+ ctx.otpKey = authData.otpKey;
+
+ const otpSubmitButton = ctx.parsedBody?.otp;
+ switch (otpSubmitButton) {
+ case 'disable':
+ await this._otpDisable(dbCtx, ctx, authData);
+ return;
+
+ case 'confirm':
+ await this._otpConfirm(dbCtx, ctx);
+ return;
+
+ case 'enable':
+ await this._otpEnable(ctx);
+ return;
+ }
+
+ const credentialSubmitButton = ctx.parsedBody?.credential;
+ switch (credentialSubmitButton) { // eslint-disable-line sonarjs/no-small-switch
+ case 'update':
+ await this._credentialUpdate(dbCtx, ctx, authData);
+ return;
+ }
+ }); // dbCtx
+ } catch (e) {
+ this.logger.error(_scope, 'failed', { ctx, error: e });
+ ctx.errors.push('An error was encountered. Sorry that is not very helpful.');
+ }
+
+ res.end(Template.SettingsHTML(ctx, this.options, appCb));
+ this.logger.info(_scope, 'finished', { ctx });
+ }
+
+
+ /**
+ * Submission to disable OTP.
+ * @param {*} dbCtx
+ * @param {*} ctx
+ * @param {AuthInfo} authData
+ */
+ async _otpDisable(dbCtx, ctx, authData) {
+ const _scope = _fileScope('_otpDisable');
+ try {
+ authData.otpKey = null;
+ await this.db.authenticationUpdateOTPKey(dbCtx, ctx.authenticationId, null);
+ ctx.notifications.push('OTP removed!');
+ delete ctx.otpKey;
+ this.logger.info(_scope, 'otp disabled', { identifier: ctx.authenticationId });
+ } catch (e) {
+ this.logger.error(_scope, 'failed', { error: e, ctx });
+ ctx.errors.push('Failed to disable OTP!');
+ }
+ }
+
+
+ /**
+ * Submission to enable OTP.
+ * @param {Object} ctx
+ */
+ async _otpEnable(ctx) {
+ const _scope = _fileScope('_otpEnable');
+ try {
+ ctx.otpConfirmKey = await TOTP.createKey('sha1', 'base32');
+ ctx.otpConfirmBox = await this.mysteryBox.pack({
+ otpKey: ctx.otpConfirmKey,
+ otpAttempt: 0,
+ otpInitiatedMs: Date.now(),
+ });
+ } catch (e) {
+ delete ctx.otpConfirmKey;
+ delete ctx.otpConfirmBox;
+ this.logger.error(_scope, 'failed', { error: e, ctx });
+ ctx.errors.push('Failed to enable OTP!');
+ }