mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
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:
@@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
Reference in New Issue
Block a user