server: migrations: add migration for UserInDatabase2

This commit is contained in:
Jesse Chan
2020-10-11 11:32:24 +08:00
parent cf08d68c92
commit 34b5e09753
6 changed files with 125 additions and 20 deletions
+96
View File
@@ -0,0 +1,96 @@
import {AccessLevel} from '../../../shared/schema/Auth';
import Users from '../../models/Users';
import type {Credentials} from '../../../shared/schema/Auth';
import type {RTorrentConnectionSettings} from '../../../shared/schema/ClientConnectionSettings';
import type {UserInDatabase1} from './types/UserInDatabase1';
const migrationError = (err?: Error) => {
if (err) {
console.error(err);
}
console.error('Migration failed. You need to reset the databases manually.');
process.exit();
};
const migration = () => {
return new Promise((resolve, _reject) => {
Users.listUsers((users, err) => {
if (users == null || err) {
return;
}
Promise.all(
users.map((user) => {
return new Promise((migratedResolve, _migratedReject) => {
if (user.client != null) {
// No need to migrate.
migratedResolve();
return;
}
const userV1 = (user as unknown) as UserInDatabase1;
let connectionSettings: RTorrentConnectionSettings | null = null;
if (userV1.socketPath != null) {
connectionSettings = {
client: 'rTorrent',
type: 'socket',
version: 1,
socket: userV1.socketPath,
};
} else if (userV1.host != null && userV1.port != null) {
connectionSettings = {
client: 'rTorrent',
type: 'tcp',
version: 1,
host: userV1.host,
port: userV1.port,
};
}
if (connectionSettings == null) {
migrationError(new Error('Corrupted client connection settings.'));
return;
}
const userV2: Credentials = {
username: userV1.username,
password: userV1.password,
client: connectionSettings,
level: userV1.isAdmin ? AccessLevel.ADMINISTRATOR : AccessLevel.USER,
};
Users.removeUser(userV1.username, (id, errRemoval) => {
if (errRemoval) {
migrationError(errRemoval);
return;
}
if (id == null) {
migrationError(new Error('Wrong user ID'));
return;
}
Users.createUser(
userV2,
(_username, errCreation) => {
if (errCreation) {
migrationError(errCreation);
}
migratedResolve();
},
false,
);
});
});
}),
).then(() => {
resolve();
});
});
});
};
export default migration;
+3 -5
View File
@@ -1,8 +1,6 @@
const migrations = [
() => {
// do nothing. there is no migration at the moment.
},
];
import UserInDatabase2 from './UserInDatabase2';
const migrations = [UserInDatabase2];
const migrate = () => Promise.all(migrations.map((migration) => migration()));
@@ -0,0 +1,10 @@
// Deprecated data structure. Not used outside of migration.
export type UserInDatabase1 = {
_id: string;
username: string;
password: string;
host?: string | null;
port?: number | null;
socketPath?: string | null;
isAdmin: boolean;
};
+5 -2
View File
@@ -4,13 +4,16 @@ import chalk from 'chalk';
import enforcePrerequisites from './enforce-prerequisites';
import migrateData from './migrations/run';
import startWebServer from './web-server';
process.env.NODE_ENV = process.env.NODE_ENV !== 'development' ? 'production' : 'development';
enforcePrerequisites()
.then(migrateData)
.then(startWebServer)
.then(() => {
// We do this because we don't want the side effects of importing server functions before migration is completed.
const startWebServer = require('./web-server').default; // eslint-disable-line global-require
return startWebServer();
})
.catch((error) => {
console.log(chalk.red('Failed to start Flood:'));
console.trace(error);
+4 -4
View File
@@ -75,7 +75,8 @@ class Users {
createUser(
credentials: Credentials,
callback: (data: {username: Required<Credentials['username']>} | null, error?: Error) => void,
callback: (user: UserInDatabase | null, error?: Error) => void,
shouldHash = true,
): void {
if (this.db == null) {
return callback(null, new Error('Users database is not ready.'));
@@ -87,7 +88,7 @@ class Users {
this.db.insert(
{
...credentials,
password: hash.encoded,
password: shouldHash ? hash.encoded : credentials.password,
},
(error, user) => {
if (error) {
@@ -98,8 +99,7 @@ class Users {
return callback(null, error);
}
services.bootstrapServicesForUser(user as UserInDatabase);
return callback({username: credentials.username});
return callback(user as UserInDatabase);
},
);
})
+7 -9
View File
@@ -92,11 +92,7 @@ router.post<unknown, unknown, AuthAuthenticationOptions>('/authenticate', (req,
const credentials = parsedResult.data;
Users.comparePassword(credentials, (isMatch, level, err) => {
if (err) {
return;
}
Users.comparePassword(credentials, (isMatch, level, _err) => {
if (isMatch === true && level != null) {
sendAuthenticationResponse(res, {
...credentials,
@@ -164,14 +160,16 @@ router.post<unknown, unknown, AuthRegistrationOptions, {cookie: string}>('/regis
const credentials = parsedResult.data;
// Attempt to save the user
Users.createUser(credentials, (createUserResponse, createUserError) => {
if (createUserError) {
ajaxUtil.getResponseFn(res)(createUserResponse, createUserError);
Users.createUser(credentials, (user, error) => {
if (error || user == null) {
ajaxUtil.getResponseFn(res)({username: credentials.username}, error);
return;
}
services.bootstrapServicesForUser(user);
if (req.query.cookie === 'false') {
ajaxUtil.getResponseFn(res)(createUserResponse);
ajaxUtil.getResponseFn(res)({username: user.username});
return;
}