User consent for model training (#4)
* Removed older 2fa/oauth code in favour of reworking * add user settings, add checkbox to share data on /useradmin/overview page * Add settings api, update modelsmain^2
parent
6f0b627a61
commit
cfa853fd15
|
@ -0,0 +1,50 @@
|
||||||
|
NODE_ENV=development
|
||||||
|
APP_SALT=RANDOM_SEED
|
||||||
|
LOG_LEVEL=debug
|
||||||
|
|
||||||
|
DB_NAME=retro-pilot
|
||||||
|
DB_USER=root
|
||||||
|
DB_PASS=root
|
||||||
|
# If using docker compose, this should match the container service name
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=5432
|
||||||
|
# Whether or not to DROP all tables and recreate to match the current models
|
||||||
|
DB_FORCE_SYNC=false
|
||||||
|
|
||||||
|
ALLOW_REGISTRATION=true
|
||||||
|
AUTH_2FA_ISSUER=RetroPilot
|
||||||
|
|
||||||
|
HTTP_INTERFACE=0.0.0.0
|
||||||
|
HTTP_PORT=8080
|
||||||
|
|
||||||
|
# Set to false to skip sending mail, all attempted mail is logged under DEBUG
|
||||||
|
CAN_SEND_MAIL=false
|
||||||
|
# credentials for smtp server to send account registration mails. if not filled in, get the generated tokens from the server.log manually
|
||||||
|
SMTP_HOST="localhost"
|
||||||
|
SMTP_PORT=25
|
||||||
|
SMTP_USER=root
|
||||||
|
SMTP_PASS=
|
||||||
|
SMTP_FROM="no-reply@retropilot.org"
|
||||||
|
|
||||||
|
# base url of the retropilot server
|
||||||
|
BASE_URL="http://192.168.1.165:8080/"
|
||||||
|
# base url sent to devices for POSTing drives & logs
|
||||||
|
BASE_UPLOAD_URL="http://192.168.1.165:8080/backend/post_upload"
|
||||||
|
# base download url for drive & log data
|
||||||
|
BASE_DRIVE_DOWNLOAD_URL="http://192.168.1.165:8080/realdata/"
|
||||||
|
# path mapping of above download url for expressjs, prefix with "/"
|
||||||
|
BASE_DRIVE_DOWNLOAD_PATH_MAPPING="/realdata"
|
||||||
|
# relative or absolute ( "/..." for absolute path )
|
||||||
|
STORAGE_PATH="realdata/"
|
||||||
|
|
||||||
|
CABANA_URL="http://192.168.1.165:8080/cabana/index.html"
|
||||||
|
|
||||||
|
DEVICE_STORAGE_QUOTA_MB=200000
|
||||||
|
DEVICE_EXPIRATION_DAYS=30
|
||||||
|
|
||||||
|
WELCOME_MESSAGE="<><><><><><><><><><><><><><><><><><><><><><><br>2022 RetroPilot"
|
||||||
|
|
||||||
|
USE_USER_ADMIN_API=0
|
||||||
|
|
||||||
|
CLIENT_SOCKET_PORT=81
|
||||||
|
CLIENT_SOCKET_HOST="0.0.0.0"
|
File diff suppressed because it is too large
Load Diff
|
@ -49,6 +49,7 @@
|
||||||
"sanitize": "^2.1.0",
|
"sanitize": "^2.1.0",
|
||||||
"sequelize": "^6.17.0",
|
"sequelize": "^6.17.0",
|
||||||
"simple-oauth2": "^4.3.0",
|
"simple-oauth2": "^4.3.0",
|
||||||
|
"spm-agent-nodejs": "^4.1.4",
|
||||||
"sqlite": "^4.0.25",
|
"sqlite": "^4.0.25",
|
||||||
"sqlite3": "^5.0.2",
|
"sqlite3": "^5.0.2",
|
||||||
"supertest": "^6.2.2",
|
"supertest": "^6.2.2",
|
||||||
|
|
|
@ -45,6 +45,11 @@ const Accounts = sequelize.define('accounts', {
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
type: DataTypes.BOOLEAN,
|
type: DataTypes.BOOLEAN,
|
||||||
},
|
},
|
||||||
|
research_enabled: {
|
||||||
|
allowNull: false,
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
timestamps: false,
|
timestamps: false,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
import { DataTypes } from 'sequelize';
|
|
||||||
|
|
||||||
import sequelize from './orm';
|
|
||||||
|
|
||||||
const AthenaActionLog = sequelize.define('athena_action_log', {
|
|
||||||
id: {
|
|
||||||
allowNull: false,
|
|
||||||
autoIncrement: true,
|
|
||||||
primaryKey: true,
|
|
||||||
type: DataTypes.INTEGER,
|
|
||||||
},
|
|
||||||
account_id: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.INTEGER,
|
|
||||||
},
|
|
||||||
device_id: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.INTEGER,
|
|
||||||
},
|
|
||||||
action: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
},
|
|
||||||
user_ip: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
},
|
|
||||||
device_ip: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
},
|
|
||||||
meta: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
},
|
|
||||||
created_at: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.BIGINT,
|
|
||||||
},
|
|
||||||
dongle_id: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
timestamps: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default AthenaActionLog;
|
|
|
@ -1,40 +0,0 @@
|
||||||
import { DataTypes } from 'sequelize';
|
|
||||||
|
|
||||||
import sequelize from './orm';
|
|
||||||
|
|
||||||
const AthenaReturnedData = sequelize.define('athena_returned_data', {
|
|
||||||
id: {
|
|
||||||
allowNull: false,
|
|
||||||
autoIncrement: true,
|
|
||||||
primaryKey: true,
|
|
||||||
type: DataTypes.INTEGER,
|
|
||||||
},
|
|
||||||
device_id: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.INTEGER,
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
},
|
|
||||||
created_at: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
},
|
|
||||||
uuid: {
|
|
||||||
allowNull: false,
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
},
|
|
||||||
resolved_at: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.INTEGER,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
timestamps: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default AthenaReturnedData;
|
|
|
@ -1,40 +0,0 @@
|
||||||
import { DataTypes } from 'sequelize';
|
|
||||||
|
|
||||||
import sequelize from './orm';
|
|
||||||
|
|
||||||
const DeviceAuthorisedUsers = sequelize.define('device_authorised_users', {
|
|
||||||
id: {
|
|
||||||
allowNull: false,
|
|
||||||
autoIncrement: true,
|
|
||||||
primaryKey: true,
|
|
||||||
type: DataTypes.INTEGER,
|
|
||||||
},
|
|
||||||
account_id: {
|
|
||||||
allowNull: false,
|
|
||||||
type: DataTypes.INTEGER,
|
|
||||||
},
|
|
||||||
device_id: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.INTEGER,
|
|
||||||
},
|
|
||||||
athena: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.BOOLEAN,
|
|
||||||
},
|
|
||||||
unpair: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.BOOLEAN,
|
|
||||||
},
|
|
||||||
view_drives: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.BOOLEAN,
|
|
||||||
},
|
|
||||||
created_at: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.BIGINT,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
timestamps: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default DeviceAuthorisedUsers;
|
|
|
@ -1,23 +1,15 @@
|
||||||
import Accounts from './accounts.model';
|
import Accounts from './accounts.model';
|
||||||
import AthenaActionLog from './athena_action_log.model';
|
|
||||||
import AthenaReturnedData from './athena_returned_data.model';
|
|
||||||
import DeviceAuthorisedUsers from './device_authorised_users.model';
|
|
||||||
import Devices from './devices.model';
|
import Devices from './devices.model';
|
||||||
import DriveSegments from './drive_segments.model';
|
import DriveSegments from './drive_segments.model';
|
||||||
import Drives from './drives.model';
|
import Drives from './drives.model';
|
||||||
import OAuthAccounts from './oauth_accounts.model';
|
|
||||||
|
|
||||||
import orm from './orm';
|
import orm from './orm';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Accounts,
|
Accounts,
|
||||||
AthenaActionLog,
|
|
||||||
AthenaReturnedData,
|
|
||||||
DeviceAuthorisedUsers,
|
|
||||||
Devices,
|
Devices,
|
||||||
DriveSegments,
|
DriveSegments,
|
||||||
Drives,
|
Drives,
|
||||||
OAuthAccounts,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default orm;
|
export default orm;
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
import { DataTypes } from 'sequelize';
|
|
||||||
|
|
||||||
import sequelize from './orm';
|
|
||||||
|
|
||||||
const OAuthAccounts = sequelize.define('oauth_accounts', {
|
|
||||||
id: {
|
|
||||||
id: false,
|
|
||||||
autoIncrement: true,
|
|
||||||
primaryKey: true,
|
|
||||||
type: DataTypes.INTEGER,
|
|
||||||
},
|
|
||||||
account_id: {
|
|
||||||
allowNull: false,
|
|
||||||
type: DataTypes.INTEGER,
|
|
||||||
},
|
|
||||||
email: {
|
|
||||||
allowNull: false,
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
},
|
|
||||||
created: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.TIME,
|
|
||||||
},
|
|
||||||
last_used: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.CHAR,
|
|
||||||
},
|
|
||||||
refresh: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
},
|
|
||||||
provider: {
|
|
||||||
allowNull: true,
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
timestamps: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default OAuthAccounts;
|
|
|
@ -1,4 +1,3 @@
|
||||||
// TODO move everythijng away from this dumb intertwined style
|
|
||||||
|
|
||||||
import authentication from './authentication';
|
import authentication from './authentication';
|
||||||
import { Accounts } from '../../models';
|
import { Accounts } from '../../models';
|
||||||
|
|
|
@ -67,6 +67,8 @@ async function changePassword(account, newPassword, oldPassword) {
|
||||||
async function getAuthenticatedAccount(req) {
|
async function getAuthenticatedAccount(req) {
|
||||||
const sessionJWT = req.cookies.jwt;
|
const sessionJWT = req.cookies.jwt;
|
||||||
if ((!sessionJWT || sessionJWT.expires <= Date.now())) {
|
if ((!sessionJWT || sessionJWT.expires <= Date.now())) {
|
||||||
|
console.log('Authentication - expired JWT session provided', sessionJWT);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,14 +77,18 @@ async function getAuthenticatedAccount(req) {
|
||||||
|
|
||||||
async function getAccountFromJWT(jwt, limitData = true) {
|
async function getAccountFromJWT(jwt, limitData = true) {
|
||||||
let token;
|
let token;
|
||||||
|
console.log(jwt);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
token = jsonwebtoken.verify(jwt, process.env.APP_SALT);
|
token = jsonwebtoken.verify(jwt, process.env.APP_SALT);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.log(jwt, 'bad jwt');
|
||||||
return null;// {success: false, msg: 'BAD_JWT'}
|
return null;// {success: false, msg: 'BAD_JWT'}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!token || !token.accountId) {
|
if (!token || !token.accountId) {
|
||||||
|
console.log(jwt, 'bad token');
|
||||||
|
|
||||||
return null; // {success: false, badToken: true}
|
return null; // {success: false, badToken: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,6 +103,8 @@ async function getAccountFromJWT(jwt, limitData = true) {
|
||||||
|
|
||||||
const account = await Accounts.findOne(query);
|
const account = await Accounts.findOne(query);
|
||||||
if (!account || !account.dataValues) {
|
if (!account || !account.dataValues) {
|
||||||
|
console.log(jwt, 'invalid');
|
||||||
|
|
||||||
return null; // {success: false, isInvalid: true}
|
return null; // {success: false, isInvalid: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,6 +118,8 @@ async function getAccountFromJWT(jwt, limitData = true) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!account || account.banned) {
|
if (!account || account.banned) {
|
||||||
|
console.log(jwt, 'banned');
|
||||||
|
|
||||||
return null; // {success: false, isBanned: true}
|
return null; // {success: false, isBanned: true}
|
||||||
}
|
}
|
||||||
return account;
|
return account;
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
import jsonwebtoken from 'jsonwebtoken';
|
|
||||||
import { AuthorizationCode } from 'simple-oauth2';
|
|
||||||
import log4js from 'log4js';
|
|
||||||
import { AUTH_OAUTH_ERR_GOOGLE, AUTH_OAUTH_ERR_GOOGLE_FAILED_TOKEN_FETCH } from '../../../consistency/terms';
|
|
||||||
|
|
||||||
const logger = log4js.getLogger();
|
|
||||||
|
|
||||||
const keys = {
|
|
||||||
web: {
|
|
||||||
client_id: '816666184056-n2cpdtsf2v9iiv81ro80cckl5f4oi4p8.apps.googleusercontent.com', project_id: 'glassy-tube-338505', auth_uri: 'https://accounts.google.com/o/oauth2/auth', token_uri: 'https://oauth2.googleapis.com/token', auth_provider_x509_cert_url: 'https://www.googleapis.com/oauth2/v1/certs', client_secret: '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', redirect_uris: ['http://localhost/authentication/oauth/callback'], javascript_origins: ['http://localhost'],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
client: {
|
|
||||||
id: keys.web.client_id,
|
|
||||||
secret: keys.web.client_secret,
|
|
||||||
},
|
|
||||||
auth: {
|
|
||||||
// token server
|
|
||||||
tokenHost: 'https://oauth2.googleapis.com',
|
|
||||||
tokenPath: '/token',
|
|
||||||
|
|
||||||
// authorization server
|
|
||||||
authorizeHost: 'https://accounts.google.com',
|
|
||||||
authorizePath: '/o/oauth2/v2/auth',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function getToken(code, scope) {
|
|
||||||
const client = new AuthorizationCode(config);
|
|
||||||
|
|
||||||
const tokenParams = {
|
|
||||||
code,
|
|
||||||
redirect_uri: 'http://localhost/authentication/oauth/callback',
|
|
||||||
scope,
|
|
||||||
};
|
|
||||||
|
|
||||||
let accessToken;
|
|
||||||
|
|
||||||
try {
|
|
||||||
accessToken = await client.getToken(tokenParams);
|
|
||||||
} catch (error) {
|
|
||||||
logger.warn(AUTH_OAUTH_ERR_GOOGLE, AUTH_OAUTH_ERR_GOOGLE_FAILED_TOKEN_FETCH, error);
|
|
||||||
return { error: true, ...AUTH_OAUTH_ERR_GOOGLE_FAILED_TOKEN_FETCH };
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`accessToken: ${accessToken}`);
|
|
||||||
|
|
||||||
const id = jsonwebtoken.decode(accessToken.token.id_token);
|
|
||||||
|
|
||||||
logger.info(`jsonwebtoken.${id}`);
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getURL() {
|
|
||||||
const client = new AuthorizationCode(config);
|
|
||||||
|
|
||||||
return client.authorizeURL({
|
|
||||||
redirect_uri: 'http://localhost/authentication/oauth/callback',
|
|
||||||
scope: 'https://www.googleapis.com/auth/userinfo.email',
|
|
||||||
state: 'ada',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
getToken,
|
|
||||||
getURL,
|
|
||||||
};
|
|
|
@ -1 +0,0 @@
|
||||||
// empty
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { Accounts } from '../../../models';
|
||||||
|
|
||||||
|
export async function SetResearchStatus(userId, status) {
|
||||||
|
if (typeof (status) !== 'boolean') { return { success: false, notBoolean: true }; }
|
||||||
|
|
||||||
|
Accounts.update({ research_enabled: status }, { where: { id: userId } });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GetResearchStatus(userId) {
|
||||||
|
return Accounts.findOne({where: {id: userId}, attributes: ['research_enabled']})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default null;
|
|
@ -7,6 +7,7 @@ export const getAccount = async (req, res, next) => {
|
||||||
|
|
||||||
export const requireAuthenticated = async (req, res, next) => {
|
export const requireAuthenticated = async (req, res, next) => {
|
||||||
const account = await authenticationController.getAuthenticatedAccount(req);
|
const account = await authenticationController.getAuthenticatedAccount(req);
|
||||||
|
console.log(account);
|
||||||
if (!account) {
|
if (!account) {
|
||||||
res.status(401).json({
|
res.status(401).json({
|
||||||
success: false,
|
success: false,
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
import express from 'express';
|
|
||||||
import log4js from 'log4js';
|
|
||||||
|
|
||||||
import { getURL, getToken } from '../../../controllers/authentication/oauth/google';
|
|
||||||
import { requireAuthenticated } from '../../../middlewares/authentication';
|
|
||||||
|
|
||||||
const router = express.Router();
|
|
||||||
const logger = log4js.getLogger();
|
|
||||||
|
|
||||||
router.get('/authentication/oauth/callback', async (req, res) => {
|
|
||||||
logger.info(req.query);
|
|
||||||
res.json(await getToken(req.query.code, req.query.scope));
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/authentication/oauth/:provider', async (req, res) => {
|
|
||||||
const { provider } = req.params;
|
|
||||||
logger.info('provider', provider);
|
|
||||||
let url;
|
|
||||||
switch (provider) {
|
|
||||||
case 'google':
|
|
||||||
url = await getURL();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
url = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url) {
|
|
||||||
res.redirect(url);
|
|
||||||
} else {
|
|
||||||
res.json({ error: true, msg: 'Invalid provider' });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
router.get('/authentication/oauth/pair/:provider', requireAuthenticated, async (req, res) => {
|
|
||||||
res.status(200);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
|
@ -1,11 +0,0 @@
|
||||||
import express from 'express';
|
|
||||||
|
|
||||||
import { requireAuthenticated } from '../../../middlewares/authentication';
|
|
||||||
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
router.get('/authentication/twofactor/enrol', requireAuthenticated, async () => {
|
|
||||||
// TODO: implementation
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
|
@ -5,6 +5,7 @@ import admin from './admin';
|
||||||
import auth from './auth';
|
import auth from './auth';
|
||||||
import devices from './devices';
|
import devices from './devices';
|
||||||
import useradmin from './useradmin';
|
import useradmin from './useradmin';
|
||||||
|
import userSettings from './user/settings';
|
||||||
|
|
||||||
// /api
|
// /api
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
@ -13,6 +14,9 @@ router.use('/admin', admin);
|
||||||
router.use('/auth', auth);
|
router.use('/auth', auth);
|
||||||
router.use('/devices', devices);
|
router.use('/devices', devices);
|
||||||
router.use('/useradmin', useradmin);
|
router.use('/useradmin', useradmin);
|
||||||
|
router.use('/user/settings', userSettings);
|
||||||
|
|
||||||
|
console.log(userSettings);
|
||||||
|
|
||||||
// TODO: setup oauth and twofactor endpoints
|
// TODO: setup oauth and twofactor endpoints
|
||||||
// app.use(routers.oauthAuthenticator);
|
// app.use(routers.oauthAuthenticator);
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import bodyParser from 'body-parser';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import dirTree from 'directory-tree';
|
||||||
|
import express from 'express';
|
||||||
|
import log4js from 'log4js';
|
||||||
|
|
||||||
|
import { requireAuthenticated } from '../../../middlewares/authentication';
|
||||||
|
import { SetResearchStatus, GetResearchStatus } from '../../../controllers/user/settings';
|
||||||
|
|
||||||
|
const logger = log4js.getLogger();
|
||||||
|
|
||||||
|
// /api/devices
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.patch('/research/:enabled', requireAuthenticated, async (req, res) => {
|
||||||
|
const { enabled } = req.params;
|
||||||
|
if (!enabled) { res.json({ bad: true }); }
|
||||||
|
const doEnable = enabled === 'true';
|
||||||
|
const accountId = req.account.id;
|
||||||
|
|
||||||
|
const update = await SetResearchStatus(req.account.id, doEnable);
|
||||||
|
|
||||||
|
return res.json({ success: true, data: req.account });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/research/', requireAuthenticated, async (req, res) => {
|
||||||
|
const accountId = req.account.id;
|
||||||
|
|
||||||
|
const update = await GetResearchStatus(req.account.id);
|
||||||
|
|
||||||
|
return res.json({ success: true, data: update });
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
|
@ -214,7 +214,30 @@ router.get('/overview', requireAuthenticated, runAsyncWrapper(async (req, res) =
|
||||||
<b>Account:</b> #${account.id}<br>
|
<b>Account:</b> #${account.id}<br>
|
||||||
<b>Email:</b> ${account.email}<br>
|
<b>Email:</b> ${account.email}<br>
|
||||||
<b>Created:</b> ${helperController.formatDate(account.created)}<br><br>
|
<b>Created:</b> ${helperController.formatDate(account.created)}<br><br>
|
||||||
|
<b>User Settings</b>
|
||||||
|
<br>
|
||||||
|
<input type="checkbox" id="share-data" name="sharedata"
|
||||||
|
checked>
|
||||||
|
<label for="scales">Share drives for community model development</label><br>
|
||||||
|
<script>
|
||||||
|
const selectElement = document.querySelector('#share-data');
|
||||||
|
fetch('/api/user/settings/research')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data.success) {
|
||||||
|
selectElement.checked = data.data.research_enabled
|
||||||
|
}
|
||||||
|
});
|
||||||
|
selectElement.addEventListener('change', (event) => {
|
||||||
|
fetch('/api/user/settings/research/'+event.target.checked, {
|
||||||
|
method: 'PATCH'})
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<br><br>
|
||||||
|
|
||||||
<b>Devices:</b><br>
|
<b>Devices:</b><br>
|
||||||
|
|
||||||
|
|
||||||
<table border=1 cellpadding=2 cellspacing=2>
|
<table border=1 cellpadding=2 cellspacing=2>
|
||||||
<tr><th>dongle_id</th><th>device_type</th><th>created</th><th>last_ping</th><th>storage_used</th></tr>
|
<tr><th>dongle_id</th><th>device_type</th><th>created</th><th>last_ping</th><th>storage_used</th></tr>
|
||||||
`;
|
`;
|
||||||
|
@ -230,6 +253,8 @@ router.get('/overview', requireAuthenticated, runAsyncWrapper(async (req, res) =
|
||||||
</tr>`;
|
</tr>`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
response += `</table>
|
response += `</table>
|
||||||
<br>
|
<br>
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|
Loading…
Reference in New Issue