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.
|
- There is a new `identity/oidc` import set that needs to be added to each config.
|
||||||
Options are `default.json` and `disabled.json`.
|
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 `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 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.
|
- 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.
|
- 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-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
|
* *initialize-intro*: Similar to `initialize-prefilled-root` but adds an index page
|
||||||
specific to the memory-based server of the default configuration.
|
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.
|
* *static-root*: Shows a static introduction page at the server root. This is not a Solid resource.
|
||||||
|
|
||||||
## Main
|
## 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';
|
export * from './identity/storage/WebIdAdapterFactory';
|
||||||
|
|
||||||
// Identity
|
// Identity
|
||||||
|
export * from './identity/AccountInitializer';
|
||||||
export * from './identity/IdentityProviderHttpHandler';
|
export * from './identity/IdentityProviderHttpHandler';
|
||||||
|
export * from './identity/IdentityUtil';
|
||||||
export * from './identity/OidcHttpHandler';
|
export * from './identity/OidcHttpHandler';
|
||||||
|
|
||||||
// Init/Cluster
|
// 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