feat: Full rework of account management

Complete rewrite of the account management and related systems.
Makes the architecture more modular,
allowing for easier extensions and configurations.
This commit is contained in:
Joachim Van Herwegen
2022-03-16 10:12:13 +01:00
parent ade977bb4f
commit a47f5236ef
366 changed files with 12345 additions and 5111 deletions

View File

@@ -0,0 +1,63 @@
import { randomBytes } from 'crypto';
import { getLoggerFor } from '../../../../logging/LogUtil';
import type { KeyValueStorage } from '../../../../storage/keyvalue/KeyValueStorage';
import { BadRequestHttpError } from '../../../../util/errors/BadRequestHttpError';
import type { Account } from '../../account/util/Account';
import type { AccountStore } from '../../account/util/AccountStore';
import { safeUpdate } from '../../account/util/AccountUtil';
import type { ClientCredentialsIdRoute } from './ClientCredentialsIdRoute';
import type { ClientCredentials, ClientCredentialsStore } from './ClientCredentialsStore';
/**
* A {@link ClientCredentialsStore} that uses a {@link KeyValueStorage} for storing the tokens.
*/
export class BaseClientCredentialsStore implements ClientCredentialsStore {
private readonly logger = getLoggerFor(this);
private readonly clientCredentialsRoute: ClientCredentialsIdRoute;
private readonly accountStore: AccountStore;
private readonly storage: KeyValueStorage<string, ClientCredentials>;
public constructor(clientCredentialsRoute: ClientCredentialsIdRoute, accountStore: AccountStore,
storage: KeyValueStorage<string, ClientCredentials>) {
this.clientCredentialsRoute = clientCredentialsRoute;
this.accountStore = accountStore;
this.storage = storage;
}
public async get(id: string): Promise<ClientCredentials | undefined> {
return this.storage.get(id);
}
public async add(id: string, webId: string, account: Account): Promise<{ secret: string; resource: string }> {
if (typeof account.webIds[webId] !== 'string') {
this.logger.warn(`Trying to create token for ${webId} which does not belong to account ${account.id}`);
throw new BadRequestHttpError('WebID does not belong to this account.');
}
const secret = randomBytes(64).toString('hex');
const resource = this.clientCredentialsRoute.getPath({ accountId: account.id, clientCredentialsId: id });
account.clientCredentials[id] = resource;
await safeUpdate(account,
this.accountStore,
(): Promise<any> => this.storage.set(id, { accountId: account.id, secret, webId }));
this.logger.debug(`Created client credentials token ${id} for WebID ${webId} and account ${account.id}`);
return { secret, resource };
}
public async delete(id: string, account: Account): Promise<void> {
const link = account.clientCredentials[id];
if (link) {
delete account.clientCredentials[id];
await safeUpdate(account,
this.accountStore,
(): Promise<any> => this.storage.delete(id));
this.logger.debug(`Deleted client credentials token ${id} for account ${account.id}`);
}
}
}

View File

@@ -0,0 +1,20 @@
import type { AccountIdKey, AccountIdRoute } from '../../account/AccountIdRoute';
import { IdInteractionRoute } from '../../routing/IdInteractionRoute';
import type { ExtendedRoute } from '../../routing/InteractionRoute';
export type CredentialsIdKey = 'clientCredentialsId';
/**
* An {@link AccountIdRoute} that also includes a credentials identifier.
*/
export type ClientCredentialsIdRoute = ExtendedRoute<AccountIdRoute, CredentialsIdKey>;
/**
* Implementation of an {@link ClientCredentialsIdRoute}
* that adds the identifier relative to a base {@link AccountIdRoute}.
*/
export class BaseClientCredentialsIdRoute extends IdInteractionRoute<AccountIdKey, CredentialsIdKey> {
public constructor(base: AccountIdRoute) {
super(base, 'clientCredentialsId');
}
}

View File

@@ -0,0 +1,47 @@
import type { Account } from '../../account/util/Account';
/**
* A client credentials token.
* If at some point the WebID is no longer registered to the account stored in this token,
* the token should be invalidated.
*/
export interface ClientCredentials {
/**
* The identifier of the account that created the token.
*/
accountId: string;
/**
* The secret of the token.
*/
secret: string;
/**
* The WebID users will be identified as after using the token.
*/
webId: string;
}
/**
* Stores and creates {@link ClientCredentials}.
*/
export interface ClientCredentialsStore {
/**
* Find the {@link ClientCredentials} with the given label. Undefined if there is no match.
* @param label - Label of the credentials.
*/
get: (label: string) => Promise<ClientCredentials | undefined>;
/**
* Creates new {@link ClientCredentials} and adds a reference to the account.
* Will error if the WebID is not registered to the account.
*
* @param label - Identifier to use for the new credentials.
* @param webId - WebID to identify as when using this token.
* @param account - Account that is associated with this token.
*/
add: (label: string, webId: string, account: Account) => Promise<{ secret: string; resource: string }>;
/**
* Deletes the token with the given identifier and removes the reference from the account.
* @param label - Identifier of the token.
* @param account - Account this token belongs to.
*/
delete: (label: string, account: Account) => Promise<void>;
}