mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Add support for initializing a server with a root pod
This commit is contained in:
parent
764b392b2b
commit
864dd7c2e0
@ -46,6 +46,7 @@ The following changes pertain to the imports in the default configs:
|
||||
- There is a new `identity/oidc` import set that needs to be added to each config.
|
||||
Options are `default.json` and `disabled.json`.
|
||||
- There is a new `static-root.json` import option for `app/init`, setting a static page for the root container.
|
||||
- There is a new `initialize-root-pod.json` import option for `app/init`, initializing a pod in the root.
|
||||
- There are more `identity/handler` options to finetune account management availability.
|
||||
- There is a new set of imports `storage/location` to determine where the root storage of the server is located.
|
||||
- The `app/setup`and `identity/registration` imports have been removed.
|
||||
|
@ -11,6 +11,9 @@ Contains a list of initializer that need to be run when starting the server.
|
||||
* *initialize-prefilled-root*: Similar to `initialize-root` but adds an index page to the root container.
|
||||
* *initialize-intro*: Similar to `initialize-prefilled-root` but adds an index page
|
||||
specific to the memory-based server of the default configuration.
|
||||
* *initialize-root-pod*: Initializes the server with a pod in the root.
|
||||
Email and password have not yet been set and need to be defined in the base configuration.
|
||||
See `file-root-pod.json` for an example.
|
||||
* *static-root*: Shows a static introduction page at the server root. This is not a Solid resource.
|
||||
|
||||
## Main
|
||||
|
17
config/app/init/initialize-root-pod.json
Normal file
17
config/app/init/initialize-root-pod.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld",
|
||||
"import": [
|
||||
"css:config/app/init/default.json",
|
||||
"css:config/app/init/initializers/root-pod.json"
|
||||
],
|
||||
"@graph": [
|
||||
{
|
||||
"comment": "Initializes the root container resource.",
|
||||
"@id": "urn:solid-server:default:PrimaryParallelInitializer",
|
||||
"@type": "ParallelHandler",
|
||||
"handlers": [
|
||||
{ "@id": "urn:solid-server:default:RootInitializer" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
21
config/app/init/initializers/root-pod.json
Normal file
21
config/app/init/initializers/root-pod.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld",
|
||||
"@graph": [
|
||||
{
|
||||
"comment": "Makes sure the root container exists and contains the necessary resources.",
|
||||
"@id": "urn:solid-server:default:RootInitializer",
|
||||
"@type": "ConditionalHandler",
|
||||
"storageKey": "rootInitialized",
|
||||
"storageValue": true,
|
||||
"handleStorage": true,
|
||||
"storage": { "@id": "urn:solid-server:default:SetupStorage" },
|
||||
"source": {
|
||||
"@id": "urn:solid-server:default:RootPodInitializer",
|
||||
"@type": "AccountInitializer",
|
||||
"accountStore": { "@id": "urn:solid-server:default:AccountStore" },
|
||||
"passwordStore": { "@id": "urn:solid-server:default:PasswordStore" },
|
||||
"podCreator": { "@id": "urn:solid-server:default:PodCreator" }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
51
config/file-root-pod.json
Normal file
51
config/file-root-pod.json
Normal file
@ -0,0 +1,51 @@
|
||||
{
|
||||
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^6.0.0/components/context.jsonld",
|
||||
"import": [
|
||||
"css:config/app/main/default.json",
|
||||
"css:config/app/init/initialize-root-pod.json",
|
||||
"css:config/app/variables/default.json",
|
||||
"css:config/http/handler/default.json",
|
||||
"css:config/http/middleware/default.json",
|
||||
"css:config/http/notifications/websockets.json",
|
||||
"css:config/http/server-factory/http.json",
|
||||
"css:config/http/static/default.json",
|
||||
"css:config/identity/access/public.json",
|
||||
"css:config/identity/email/default.json",
|
||||
"css:config/identity/handler/no-accounts.json",
|
||||
"css:config/identity/oidc/default.json",
|
||||
"css:config/identity/ownership/token.json",
|
||||
"css:config/identity/pod/static.json",
|
||||
"css:config/ldp/authentication/dpop-bearer.json",
|
||||
"css:config/ldp/authorization/webacl.json",
|
||||
"css:config/ldp/handler/default.json",
|
||||
"css:config/ldp/metadata-parser/default.json",
|
||||
"css:config/ldp/metadata-writer/default.json",
|
||||
"css:config/ldp/modes/default.json",
|
||||
"css:config/storage/backend/file.json",
|
||||
"css:config/storage/key-value/resource-store.json",
|
||||
"css:config/storage/location/root.json",
|
||||
"css:config/storage/middleware/default.json",
|
||||
"css:config/util/auxiliary/acl.json",
|
||||
"css:config/util/identifiers/suffix.json",
|
||||
"css:config/util/index/default.json",
|
||||
"css:config/util/logging/winston.json",
|
||||
"css:config/util/representation-conversion/default.json",
|
||||
"css:config/util/resource-locker/file.json",
|
||||
"css:config/util/variables/default.json"
|
||||
],
|
||||
"@graph": [
|
||||
{
|
||||
"comment": [
|
||||
"A Solid server that stores its resources on disk and uses WAC for authorization.",
|
||||
"A pod will be created in the root with the email/password login defined here.",
|
||||
"It is advised to immediately change this password after starting the server."
|
||||
]
|
||||
},
|
||||
{
|
||||
"@id": "urn:solid-server:default:RootPodInitializer",
|
||||
"@type": "AccountInitializer",
|
||||
"email": "test@example.com",
|
||||
"password": "secret!"
|
||||
}
|
||||
]
|
||||
}
|
70
src/identity/AccountInitializer.ts
Normal file
70
src/identity/AccountInitializer.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import { Initializer } from '../init/Initializer';
|
||||
import { getLoggerFor } from '../logging/LogUtil';
|
||||
import type { AccountStore } from './interaction/account/util/AccountStore';
|
||||
import type { PasswordStore } from './interaction/password/util/PasswordStore';
|
||||
import type { PodCreator } from './interaction/pod/util/PodCreator';
|
||||
|
||||
export interface AccountInitializerArgs {
|
||||
/**
|
||||
* Creates the accounts.
|
||||
*/
|
||||
accountStore: AccountStore;
|
||||
/**
|
||||
* Adds the login methods.
|
||||
*/
|
||||
passwordStore: PasswordStore;
|
||||
/**
|
||||
* Creates the pods.
|
||||
*/
|
||||
podCreator: PodCreator;
|
||||
/**
|
||||
* Email address for the account login.
|
||||
*/
|
||||
email: string;
|
||||
/**
|
||||
* Password for the account login.
|
||||
*/
|
||||
password: string;
|
||||
/**
|
||||
* Name to use for the pod. If undefined the pod will be made in the root of the server.
|
||||
*/
|
||||
name?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes an account with email/password login and a pod with the provided name.
|
||||
*/
|
||||
export class AccountInitializer extends Initializer {
|
||||
protected readonly logger = getLoggerFor(this);
|
||||
|
||||
private readonly accountStore: AccountStore;
|
||||
private readonly passwordStore: PasswordStore;
|
||||
private readonly podCreator: PodCreator;
|
||||
private email: string | undefined;
|
||||
private password: string | undefined;
|
||||
private readonly name: string | undefined;
|
||||
|
||||
public constructor(args: AccountInitializerArgs) {
|
||||
super();
|
||||
this.accountStore = args.accountStore;
|
||||
this.passwordStore = args.passwordStore;
|
||||
this.podCreator = args.podCreator;
|
||||
|
||||
this.email = args.email;
|
||||
this.password = args.password;
|
||||
this.name = args.name;
|
||||
}
|
||||
|
||||
public async handle(): Promise<void> {
|
||||
this.logger.info(`Creating account for ${this.email}`);
|
||||
const accountId = await this.accountStore.create();
|
||||
const id = await this.passwordStore.create(this.email!, accountId, this.password!);
|
||||
await this.passwordStore.confirmVerification(id);
|
||||
this.logger.info(`Creating pod ${this.name ? `with name ${this.name}` : 'at the root'}`);
|
||||
await this.podCreator.handleSafe({ accountId, name: this.name });
|
||||
|
||||
// Not really necessary but don't want to keep passwords in memory if not required
|
||||
delete this.email;
|
||||
delete this.password;
|
||||
}
|
||||
}
|
@ -258,7 +258,9 @@ export * from './identity/storage/PassthroughAdapterFactory';
|
||||
export * from './identity/storage/WebIdAdapterFactory';
|
||||
|
||||
// Identity
|
||||
export * from './identity/AccountInitializer';
|
||||
export * from './identity/IdentityProviderHttpHandler';
|
||||
export * from './identity/IdentityUtil';
|
||||
export * from './identity/OidcHttpHandler';
|
||||
|
||||
// Init/Cluster
|
||||
|
55
test/unit/identity/AccountInitializer.test.ts
Normal file
55
test/unit/identity/AccountInitializer.test.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { AccountInitializer } from '../../../src/identity/AccountInitializer';
|
||||
import { AccountStore } from '../../../src/identity/interaction/account/util/AccountStore';
|
||||
import { PasswordStore } from '../../../src/identity/interaction/password/util/PasswordStore';
|
||||
import { PodCreator } from '../../../src/identity/interaction/pod/util/PodCreator';
|
||||
|
||||
describe('An AccountInitializer', (): void => {
|
||||
const email = 'email@example.com';
|
||||
const password = 'password!';
|
||||
let accountStore: jest.Mocked<AccountStore>;
|
||||
let passwordStore: jest.Mocked<PasswordStore>;
|
||||
let podCreator: jest.Mocked<PodCreator>;
|
||||
let initializer: AccountInitializer;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
accountStore = {
|
||||
create: jest.fn().mockResolvedValue('account-id'),
|
||||
} satisfies Partial<AccountStore> as any;
|
||||
|
||||
passwordStore = {
|
||||
create: jest.fn().mockResolvedValue('password-id'),
|
||||
confirmVerification: jest.fn(),
|
||||
} satisfies Partial<PasswordStore> as any;
|
||||
|
||||
podCreator = {
|
||||
handleSafe: jest.fn(),
|
||||
} satisfies Partial<PodCreator> as any;
|
||||
|
||||
initializer = new AccountInitializer({
|
||||
accountStore, passwordStore, podCreator, email, password,
|
||||
});
|
||||
});
|
||||
|
||||
it('creates the account/login/pod.', async(): Promise<void> => {
|
||||
await expect(initializer.handle()).resolves.toBeUndefined();
|
||||
expect(accountStore.create).toHaveBeenCalledTimes(1);
|
||||
expect(passwordStore.create).toHaveBeenCalledTimes(1);
|
||||
expect(passwordStore.create).toHaveBeenLastCalledWith(email, 'account-id', password);
|
||||
expect(passwordStore.confirmVerification).toHaveBeenCalledTimes(1);
|
||||
expect(podCreator.handleSafe).toHaveBeenCalledTimes(1);
|
||||
expect(podCreator.handleSafe).toHaveBeenLastCalledWith({ accountId: 'account-id' });
|
||||
});
|
||||
|
||||
it('can create a pod with a name.', async(): Promise<void> => {
|
||||
initializer = new AccountInitializer({
|
||||
accountStore, passwordStore, podCreator, email, password, name: 'name',
|
||||
});
|
||||
await expect(initializer.handle()).resolves.toBeUndefined();
|
||||
expect(accountStore.create).toHaveBeenCalledTimes(1);
|
||||
expect(passwordStore.create).toHaveBeenCalledTimes(1);
|
||||
expect(passwordStore.create).toHaveBeenLastCalledWith(email, 'account-id', password);
|
||||
expect(passwordStore.confirmVerification).toHaveBeenCalledTimes(1);
|
||||
expect(podCreator.handleSafe).toHaveBeenCalledTimes(1);
|
||||
expect(podCreator.handleSafe).toHaveBeenLastCalledWith({ accountId: 'account-id', name: 'name' });
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user