feat: Add migration for v6 account data

This commit is contained in:
Joachim Van Herwegen
2023-10-04 16:38:46 +02:00
parent fedd9e04d8
commit 0ac7d407bf
39 changed files with 1034 additions and 89 deletions

View File

@@ -8,7 +8,7 @@ import { ACCOUNT_SETTINGS_REMEMBER_LOGIN } from './AccountStore';
import type { AccountLoginStorage } from './LoginStorage';
import { ACCOUNT_TYPE } from './LoginStorage';
const STORAGE_DESCRIPTION = {
export const ACCOUNT_STORAGE_DESCRIPTION = {
[ACCOUNT_SETTINGS_REMEMBER_LOGIN]: 'boolean?',
} as const;
@@ -19,7 +19,7 @@ const STORAGE_DESCRIPTION = {
export class BaseAccountStore extends Initializer implements AccountStore {
private readonly logger = getLoggerFor(this);
private readonly storage: AccountLoginStorage<{ [ACCOUNT_TYPE]: typeof STORAGE_DESCRIPTION }>;
private readonly storage: AccountLoginStorage<{ [ACCOUNT_TYPE]: typeof ACCOUNT_STORAGE_DESCRIPTION }>;
private initialized = false;
public constructor(storage: AccountLoginStorage<any>) {
@@ -33,7 +33,7 @@ export class BaseAccountStore extends Initializer implements AccountStore {
return;
}
try {
await this.storage.defineType(ACCOUNT_TYPE, STORAGE_DESCRIPTION, false);
await this.storage.defineType(ACCOUNT_TYPE, ACCOUNT_STORAGE_DESCRIPTION, false);
this.initialized = true;
} catch (cause: unknown) {
throw new InternalServerError(`Error defining account in storage: ${createErrorMessage(cause)}`, { cause });
@@ -58,6 +58,6 @@ export class BaseAccountStore extends Initializer implements AccountStore {
public async updateSetting<T extends keyof AccountSettings>(id: string, setting: T, value: AccountSettings[T]):
Promise<void> {
await this.storage.setField(ACCOUNT_TYPE, id, setting, value as ValueType<typeof STORAGE_DESCRIPTION[T]>);
await this.storage.setField(ACCOUNT_TYPE, id, setting, value as ValueType<typeof ACCOUNT_STORAGE_DESCRIPTION[T]>);
}
}

View File

@@ -7,8 +7,8 @@ import { ACCOUNT_TYPE } from '../../account/util/LoginStorage';
import type { AccountLoginStorage } from '../../account/util/LoginStorage';
import type { ClientCredentials, ClientCredentialsStore } from './ClientCredentialsStore';
const STORAGE_TYPE = 'clientCredentials';
const STORAGE_DESCRIPTION = {
export const CLIENT_CREDENTIALS_STORAGE_TYPE = 'clientCredentials';
export const CLIENT_CREDENTIALS_STORAGE_DESCRIPTION = {
label: 'string',
accountId: `id:${ACCOUNT_TYPE}`,
secret: 'string',
@@ -22,7 +22,9 @@ const STORAGE_DESCRIPTION = {
export class BaseClientCredentialsStore extends Initializer implements ClientCredentialsStore {
private readonly logger = getLoggerFor(this);
private readonly storage: AccountLoginStorage<{ [STORAGE_TYPE]: typeof STORAGE_DESCRIPTION }>;
private readonly storage: AccountLoginStorage<{ [CLIENT_CREDENTIALS_STORAGE_TYPE]:
typeof CLIENT_CREDENTIALS_STORAGE_DESCRIPTION; }>;
private initialized = false;
public constructor(storage: AccountLoginStorage<any>) {
@@ -36,9 +38,9 @@ export class BaseClientCredentialsStore extends Initializer implements ClientCre
return;
}
try {
await this.storage.defineType(STORAGE_TYPE, STORAGE_DESCRIPTION, false);
await this.storage.createIndex(STORAGE_TYPE, 'accountId');
await this.storage.createIndex(STORAGE_TYPE, 'label');
await this.storage.defineType(CLIENT_CREDENTIALS_STORAGE_TYPE, CLIENT_CREDENTIALS_STORAGE_DESCRIPTION, false);
await this.storage.createIndex(CLIENT_CREDENTIALS_STORAGE_TYPE, 'accountId');
await this.storage.createIndex(CLIENT_CREDENTIALS_STORAGE_TYPE, 'label');
this.initialized = true;
} catch (cause: unknown) {
throw new InternalServerError(`Error defining client credentials in storage: ${createErrorMessage(cause)}`,
@@ -47,11 +49,11 @@ export class BaseClientCredentialsStore extends Initializer implements ClientCre
}
public async get(id: string): Promise<ClientCredentials | undefined> {
return this.storage.get(STORAGE_TYPE, id);
return this.storage.get(CLIENT_CREDENTIALS_STORAGE_TYPE, id);
}
public async findByLabel(label: string): Promise<ClientCredentials | undefined> {
const result = await this.storage.find(STORAGE_TYPE, { label });
const result = await this.storage.find(CLIENT_CREDENTIALS_STORAGE_TYPE, { label });
if (result.length === 0) {
return;
}
@@ -59,7 +61,7 @@ export class BaseClientCredentialsStore extends Initializer implements ClientCre
}
public async findByAccount(accountId: string): Promise<ClientCredentials[]> {
return this.storage.find(STORAGE_TYPE, { accountId });
return this.storage.find(CLIENT_CREDENTIALS_STORAGE_TYPE, { accountId });
}
public async create(label: string, webId: string, accountId: string): Promise<ClientCredentials> {
@@ -69,11 +71,11 @@ export class BaseClientCredentialsStore extends Initializer implements ClientCre
`Creating client credentials token with label ${label} for WebID ${webId} and account ${accountId}`,
);
return this.storage.create(STORAGE_TYPE, { accountId, label, webId, secret });
return this.storage.create(CLIENT_CREDENTIALS_STORAGE_TYPE, { accountId, label, webId, secret });
}
public async delete(id: string): Promise<void> {
this.logger.debug(`Deleting client credentials token with ID ${id}`);
return this.storage.delete(STORAGE_TYPE, id);
return this.storage.delete(CLIENT_CREDENTIALS_STORAGE_TYPE, id);
}
}

View File

@@ -9,8 +9,8 @@ import { ACCOUNT_TYPE } from '../../account/util/LoginStorage';
import type { AccountLoginStorage } from '../../account/util/LoginStorage';
import type { PasswordStore } from './PasswordStore';
const STORAGE_TYPE = 'password';
const STORAGE_DESCRIPTION = {
export const PASSWORD_STORAGE_TYPE = 'password';
export const PASSWORD_STORAGE_DESCRIPTION = {
email: 'string',
password: 'string',
verified: 'boolean',
@@ -25,7 +25,7 @@ const STORAGE_DESCRIPTION = {
export class BasePasswordStore extends Initializer implements PasswordStore {
private readonly logger = getLoggerFor(this);
private readonly storage: AccountLoginStorage<{ [STORAGE_TYPE]: typeof STORAGE_DESCRIPTION }>;
private readonly storage: AccountLoginStorage<{ [PASSWORD_STORAGE_TYPE]: typeof PASSWORD_STORAGE_DESCRIPTION }>;
private readonly saltRounds: number;
private initialized = false;
@@ -41,9 +41,9 @@ export class BasePasswordStore extends Initializer implements PasswordStore {
return;
}
try {
await this.storage.defineType(STORAGE_TYPE, STORAGE_DESCRIPTION, true);
await this.storage.createIndex(STORAGE_TYPE, 'accountId');
await this.storage.createIndex(STORAGE_TYPE, 'email');
await this.storage.defineType(PASSWORD_STORAGE_TYPE, PASSWORD_STORAGE_DESCRIPTION, true);
await this.storage.createIndex(PASSWORD_STORAGE_TYPE, 'accountId');
await this.storage.createIndex(PASSWORD_STORAGE_TYPE, 'email');
this.initialized = true;
} catch (cause: unknown) {
throw new InternalServerError(`Error defining email/password in storage: ${createErrorMessage(cause)}`,
@@ -56,7 +56,7 @@ export class BasePasswordStore extends Initializer implements PasswordStore {
this.logger.warn(`Trying to create duplicate login for email ${email}`);
throw new BadRequestHttpError('There already is a login for this e-mail address.');
}
const payload = await this.storage.create(STORAGE_TYPE, {
const payload = await this.storage.create(PASSWORD_STORAGE_TYPE, {
accountId,
email: email.toLowerCase(),
password: await hash(password, this.saltRounds),
@@ -66,7 +66,7 @@ export class BasePasswordStore extends Initializer implements PasswordStore {
}
public async get(id: string): Promise<{ email: string; accountId: string } | undefined> {
const result = await this.storage.get(STORAGE_TYPE, id);
const result = await this.storage.get(PASSWORD_STORAGE_TYPE, id);
if (!result) {
return;
}
@@ -74,7 +74,7 @@ export class BasePasswordStore extends Initializer implements PasswordStore {
}
public async findByEmail(email: string): Promise<{ accountId: string; id: string } | undefined> {
const payload = await this.storage.find(STORAGE_TYPE, { email: email.toLowerCase() });
const payload = await this.storage.find(PASSWORD_STORAGE_TYPE, { email: email.toLowerCase() });
if (payload.length === 0) {
return;
}
@@ -82,21 +82,21 @@ export class BasePasswordStore extends Initializer implements PasswordStore {
}
public async findByAccount(accountId: string): Promise<{ id: string; email: string }[]> {
return (await this.storage.find(STORAGE_TYPE, { accountId }))
return (await this.storage.find(PASSWORD_STORAGE_TYPE, { accountId }))
.map(({ id, email }): { id: string; email: string } => ({ id, email }));
}
public async confirmVerification(id: string): Promise<void> {
if (!await this.storage.has(STORAGE_TYPE, id)) {
if (!await this.storage.has(PASSWORD_STORAGE_TYPE, id)) {
this.logger.warn(`Trying to verify unknown password login ${id}`);
throw new ForbiddenHttpError('Login does not exist.');
}
await this.storage.setField(STORAGE_TYPE, id, 'verified', true);
await this.storage.setField(PASSWORD_STORAGE_TYPE, id, 'verified', true);
}
public async authenticate(email: string, password: string): Promise<{ accountId: string; id: string }> {
const payload = await this.storage.find(STORAGE_TYPE, { email: email.toLowerCase() });
const payload = await this.storage.find(PASSWORD_STORAGE_TYPE, { email: email.toLowerCase() });
if (payload.length === 0) {
this.logger.warn(`Trying to get account info for unknown email ${email}`);
throw new ForbiddenHttpError('Invalid email/password combination.');
@@ -114,14 +114,14 @@ export class BasePasswordStore extends Initializer implements PasswordStore {
}
public async update(id: string, password: string): Promise<void> {
if (!await this.storage.has(STORAGE_TYPE, id)) {
if (!await this.storage.has(PASSWORD_STORAGE_TYPE, id)) {
this.logger.warn(`Trying to update unknown password login ${id}`);
throw new ForbiddenHttpError('Login does not exist.');
}
await this.storage.setField(STORAGE_TYPE, id, 'password', await hash(password, this.saltRounds));
await this.storage.setField(PASSWORD_STORAGE_TYPE, id, 'password', await hash(password, this.saltRounds));
}
public async delete(id: string): Promise<void> {
return this.storage.delete(STORAGE_TYPE, id);
return this.storage.delete(PASSWORD_STORAGE_TYPE, id);
}
}

View File

@@ -9,17 +9,17 @@ import { ACCOUNT_TYPE } from '../../account/util/LoginStorage';
import type { AccountLoginStorage } from '../../account/util/LoginStorage';
import type { PodStore } from './PodStore';
const STORAGE_TYPE = 'pod';
const STORAGE_DESCRIPTION = {
export const POD_STORAGE_TYPE = 'pod';
export const POD_STORAGE_DESCRIPTION = {
baseUrl: 'string',
accountId: `id:${ACCOUNT_TYPE}`,
} as const;
const OWNER_TYPE = 'owner';
const OWNER_DESCRIPTION = {
export const OWNER_STORAGE_TYPE = 'owner';
export const OWNER_STORAGE_DESCRIPTION = {
webId: 'string',
visible: 'boolean',
podId: `id:${STORAGE_TYPE}`,
podId: `id:${POD_STORAGE_TYPE}`,
} as const;
/**
@@ -35,8 +35,8 @@ export class BasePodStore extends Initializer implements PodStore {
private readonly logger = getLoggerFor(this);
private readonly storage: AccountLoginStorage<{
[STORAGE_TYPE]: typeof STORAGE_DESCRIPTION;
[OWNER_TYPE]: typeof OWNER_DESCRIPTION; }>;
[POD_STORAGE_TYPE]: typeof POD_STORAGE_DESCRIPTION;
[OWNER_STORAGE_TYPE]: typeof OWNER_STORAGE_DESCRIPTION; }>;
private readonly manager: PodManager;
private readonly visible: boolean;
@@ -56,11 +56,11 @@ export class BasePodStore extends Initializer implements PodStore {
return;
}
try {
await this.storage.defineType(STORAGE_TYPE, STORAGE_DESCRIPTION, false);
await this.storage.createIndex(STORAGE_TYPE, 'accountId');
await this.storage.createIndex(STORAGE_TYPE, 'baseUrl');
await this.storage.defineType(OWNER_TYPE, OWNER_DESCRIPTION, false);
await this.storage.createIndex(OWNER_TYPE, 'podId');
await this.storage.defineType(POD_STORAGE_TYPE, POD_STORAGE_DESCRIPTION, false);
await this.storage.createIndex(POD_STORAGE_TYPE, 'accountId');
await this.storage.createIndex(POD_STORAGE_TYPE, 'baseUrl');
await this.storage.defineType(OWNER_STORAGE_TYPE, OWNER_STORAGE_DESCRIPTION, false);
await this.storage.createIndex(OWNER_STORAGE_TYPE, 'podId');
this.initialized = true;
} catch (cause: unknown) {
throw new InternalServerError(`Error defining pods in storage: ${createErrorMessage(cause)}`,
@@ -71,14 +71,14 @@ export class BasePodStore extends Initializer implements PodStore {
public async create(accountId: string, settings: PodSettings, overwrite: boolean): Promise<string> {
// Adding pod to storage first as we cannot undo creating the pod below.
// This call might also fail because there is no login method yet on the account.
const pod = await this.storage.create(STORAGE_TYPE, { baseUrl: settings.base.path, accountId });
await this.storage.create(OWNER_TYPE, { podId: pod.id, webId: settings.webId, visible: this.visible });
const pod = await this.storage.create(POD_STORAGE_TYPE, { baseUrl: settings.base.path, accountId });
await this.storage.create(OWNER_STORAGE_TYPE, { podId: pod.id, webId: settings.webId, visible: this.visible });
try {
await this.manager.createPod(settings, overwrite);
} catch (error: unknown) {
this.logger.warn(`Pod creation failed for account ${accountId}: ${createErrorMessage(error)}`);
await this.storage.delete(STORAGE_TYPE, pod.id);
await this.storage.delete(POD_STORAGE_TYPE, pod.id);
throw new BadRequestHttpError(`Pod creation failed: ${createErrorMessage(error)}`, { cause: error });
}
this.logger.debug(`Created pod ${settings.name} for account ${accountId}`);
@@ -87,7 +87,7 @@ export class BasePodStore extends Initializer implements PodStore {
}
public async get(id: string): Promise<{ baseUrl: string; accountId: string } | undefined> {
const pod = await this.storage.get(STORAGE_TYPE, id);
const pod = await this.storage.get(POD_STORAGE_TYPE, id);
if (!pod) {
return;
}
@@ -95,7 +95,7 @@ export class BasePodStore extends Initializer implements PodStore {
}
public async findByBaseUrl(baseUrl: string): Promise<{ id: string; accountId: string } | undefined> {
const result = await this.storage.find(STORAGE_TYPE, { baseUrl });
const result = await this.storage.find(POD_STORAGE_TYPE, { baseUrl });
if (result.length === 0) {
return;
}
@@ -103,12 +103,12 @@ export class BasePodStore extends Initializer implements PodStore {
}
public async findPods(accountId: string): Promise<{ id: string; baseUrl: string }[]> {
return (await this.storage.find(STORAGE_TYPE, { accountId }))
return (await this.storage.find(POD_STORAGE_TYPE, { accountId }))
.map(({ id, baseUrl }): { id: string; baseUrl: string } => ({ id, baseUrl }));
}
public async getOwners(id: string): Promise<{ webId: string; visible: boolean }[] | undefined> {
const results = await this.storage.find(OWNER_TYPE, { podId: id });
const results = await this.storage.find(OWNER_STORAGE_TYPE, { podId: id });
if (results.length === 0) {
return;
}
@@ -119,16 +119,16 @@ export class BasePodStore extends Initializer implements PodStore {
public async updateOwner(id: string, webId: string, visible: boolean): Promise<void> {
// Need to first check if there already is an owner with the given WebID
// so we know if we need to create or update.
const matches = await this.storage.find(OWNER_TYPE, { webId, podId: id });
const matches = await this.storage.find(OWNER_STORAGE_TYPE, { webId, podId: id });
if (matches.length === 0) {
await this.storage.create(OWNER_TYPE, { webId, visible, podId: id });
await this.storage.create(OWNER_STORAGE_TYPE, { webId, visible, podId: id });
} else {
await this.storage.setField(OWNER_TYPE, matches[0].id, 'visible', visible);
await this.storage.setField(OWNER_STORAGE_TYPE, matches[0].id, 'visible', visible);
}
}
public async removeOwner(id: string, webId: string): Promise<void> {
const owners = await this.storage.find(OWNER_TYPE, { podId: id });
const owners = await this.storage.find(OWNER_STORAGE_TYPE, { podId: id });
const match = owners.find((owner): boolean => owner.webId === webId);
if (!match) {
return;
@@ -136,6 +136,6 @@ export class BasePodStore extends Initializer implements PodStore {
if (owners.length === 1) {
throw new BadRequestHttpError('Unable to remove the last owner of a pod.');
}
await this.storage.delete(OWNER_TYPE, match.id);
await this.storage.delete(OWNER_STORAGE_TYPE, match.id);
}
}

View File

@@ -7,8 +7,8 @@ import { ACCOUNT_TYPE } from '../../account/util/LoginStorage';
import type { AccountLoginStorage } from '../../account/util/LoginStorage';
import type { WebIdStore } from './WebIdStore';
const STORAGE_TYPE = 'webIdLink';
const STORAGE_DESCRIPTION = {
export const WEBID_STORAGE_TYPE = 'webIdLink';
export const WEBID_STORAGE_DESCRIPTION = {
webId: 'string',
accountId: `id:${ACCOUNT_TYPE}`,
} as const;
@@ -20,7 +20,7 @@ const STORAGE_DESCRIPTION = {
export class BaseWebIdStore extends Initializer implements WebIdStore {
private readonly logger = getLoggerFor(this);
private readonly storage: AccountLoginStorage<{ [STORAGE_TYPE]: typeof STORAGE_DESCRIPTION }>;
private readonly storage: AccountLoginStorage<{ [WEBID_STORAGE_TYPE]: typeof WEBID_STORAGE_DESCRIPTION }>;
private initialized = false;
public constructor(storage: AccountLoginStorage<any>) {
@@ -34,9 +34,9 @@ export class BaseWebIdStore extends Initializer implements WebIdStore {
return;
}
try {
await this.storage.defineType(STORAGE_TYPE, STORAGE_DESCRIPTION, false);
await this.storage.createIndex(STORAGE_TYPE, 'accountId');
await this.storage.createIndex(STORAGE_TYPE, 'webId');
await this.storage.defineType(WEBID_STORAGE_TYPE, WEBID_STORAGE_DESCRIPTION, false);
await this.storage.createIndex(WEBID_STORAGE_TYPE, 'accountId');
await this.storage.createIndex(WEBID_STORAGE_TYPE, 'webId');
this.initialized = true;
} catch (cause: unknown) {
throw new InternalServerError(`Error defining WebID links in storage: ${createErrorMessage(cause)}`,
@@ -45,16 +45,16 @@ export class BaseWebIdStore extends Initializer implements WebIdStore {
}
public async get(id: string): Promise<{ accountId: string; webId: string } | undefined> {
return this.storage.get(STORAGE_TYPE, id);
return this.storage.get(WEBID_STORAGE_TYPE, id);
}
public async isLinked(webId: string, accountId: string): Promise<boolean> {
const result = await this.storage.find(STORAGE_TYPE, { webId, accountId });
const result = await this.storage.find(WEBID_STORAGE_TYPE, { webId, accountId });
return result.length > 0;
}
public async findLinks(accountId: string): Promise<{ id: string; webId: string }[]> {
return (await this.storage.find(STORAGE_TYPE, { accountId }))
return (await this.storage.find(WEBID_STORAGE_TYPE, { accountId }))
.map(({ id, webId }): { id: string; webId: string } => ({ id, webId }));
}
@@ -64,7 +64,7 @@ export class BaseWebIdStore extends Initializer implements WebIdStore {
throw new BadRequestHttpError(`${webId} is already registered to this account.`);
}
const result = await this.storage.create(STORAGE_TYPE, { webId, accountId });
const result = await this.storage.create(WEBID_STORAGE_TYPE, { webId, accountId });
this.logger.debug(`Linked WebID ${webId} to account ${accountId}`);
@@ -73,6 +73,6 @@ export class BaseWebIdStore extends Initializer implements WebIdStore {
public async delete(linkId: string): Promise<void> {
this.logger.debug(`Deleting WebID link with ID ${linkId}`);
return this.storage.delete(STORAGE_TYPE, linkId);
return this.storage.delete(WEBID_STORAGE_TYPE, linkId);
}
}