mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
fix: Update OIDC provider dependency to v7
The biggest resulting change is that the consent page always appears after logging in. Some minor fixes to be closer to the spec are included together with some minor structural refactors.
This commit is contained in:
@@ -20,8 +20,8 @@ export class OidcHttpHandler extends HttpHandler {
|
||||
const provider = await this.providerFactory.getProvider();
|
||||
this.logger.debug(`Sending request to oidc-provider: ${request.url}`);
|
||||
// Even though the typings do not indicate this, this is a Promise that needs to be awaited.
|
||||
// Otherwise the `BaseHttpServerFactory` will write a 404 before the OIDC library could handle the response.
|
||||
// Otherwise, the `BaseHttpServerFactory` will write a 404 before the OIDC library could handle the response.
|
||||
// eslint-disable-next-line @typescript-eslint/await-thenable
|
||||
await provider.callback(request, response);
|
||||
await provider.callback()(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
import { randomBytes } from 'crypto';
|
||||
import type { JWK } from 'jose';
|
||||
import { exportJWK, generateKeyPair } from 'jose';
|
||||
import type { AnyObject,
|
||||
import type { Account,
|
||||
Adapter,
|
||||
CanBePromise,
|
||||
KoaContextWithOIDC,
|
||||
Configuration,
|
||||
Account,
|
||||
ErrorOut,
|
||||
Adapter } from 'oidc-provider';
|
||||
KoaContextWithOIDC,
|
||||
ResourceServer,
|
||||
UnknownObject } from 'oidc-provider';
|
||||
import { Provider } from 'oidc-provider';
|
||||
import type { Operation } from '../../http/Operation';
|
||||
import type { ErrorHandler } from '../../http/output/error/ErrorHandler';
|
||||
@@ -75,6 +76,7 @@ export class IdentityProviderFactory implements ProviderFactory {
|
||||
private readonly errorHandler!: ErrorHandler;
|
||||
private readonly responseWriter!: ResponseWriter;
|
||||
|
||||
private readonly jwtAlg = 'ES256';
|
||||
private provider?: Provider;
|
||||
|
||||
/**
|
||||
@@ -139,11 +141,23 @@ export class IdentityProviderFactory implements ProviderFactory {
|
||||
keys: await this.generateCookieKeys(),
|
||||
};
|
||||
|
||||
// Solid OIDC requires pkce https://solid.github.io/solid-oidc/#concepts
|
||||
config.pkce = {
|
||||
methods: [ 'S256' ],
|
||||
required: (): true => true,
|
||||
};
|
||||
|
||||
// Default client settings that might not be defined.
|
||||
// Mostly relevant for WebID clients.
|
||||
config.clientDefaults = {
|
||||
id_token_signed_response_alg: this.jwtAlg,
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a JWKS using a single RS256 JWK..
|
||||
* Generates a JWKS using a single JWK.
|
||||
* The JWKS will be cached so subsequent calls return the same key.
|
||||
*/
|
||||
private async generateJwks(): Promise<{ keys: JWK[] }> {
|
||||
@@ -153,10 +167,10 @@ export class IdentityProviderFactory implements ProviderFactory {
|
||||
return jwks;
|
||||
}
|
||||
// If they are not, generate and save them
|
||||
const { privateKey } = await generateKeyPair('RS256');
|
||||
const { privateKey } = await generateKeyPair(this.jwtAlg);
|
||||
const jwk = await exportJWK(privateKey);
|
||||
// Required for Solid authn client
|
||||
jwk.alg = 'RS256';
|
||||
jwk.alg = this.jwtAlg;
|
||||
// In node v15.12.0 the JWKS does not get accepted because the JWK is not a plain object,
|
||||
// which is why we convert it into a plain object here.
|
||||
// Potentially this can be changed at a later point in time to `{ keys: [ jwk ]}`.
|
||||
@@ -190,28 +204,51 @@ export class IdentityProviderFactory implements ProviderFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the necessary claims the to id token and access token based on the Solid OIDC spec.
|
||||
* Adds the necessary claims the to id and access tokens based on the Solid OIDC spec.
|
||||
*/
|
||||
private configureClaims(config: Configuration): void {
|
||||
// Access token audience is 'solid', ID token audience is the client_id
|
||||
config.audiences = (ctx, sub, token, use): string =>
|
||||
use === 'access_token' ? 'solid' : token.clientId!;
|
||||
|
||||
// Returns the id_token
|
||||
// See https://solid.github.io/authentication-panel/solid-oidc/#tokens-id
|
||||
// Some fields are still missing, see https://github.com/solid/community-server/issues/1154#issuecomment-1040233385
|
||||
config.findAccount = async(ctx: KoaContextWithOIDC, sub: string): Promise<Account> => ({
|
||||
accountId: sub,
|
||||
claims: async(): Promise<{ sub: string; [key: string]: any }> =>
|
||||
({ sub, webid: sub }),
|
||||
async claims(): Promise<{ sub: string; [key: string]: any }> {
|
||||
return { sub, webid: sub, azp: ctx.oidc.client?.clientId };
|
||||
},
|
||||
});
|
||||
|
||||
// Add extra claims in case an AccessToken is being issued.
|
||||
// Specifically this sets the required webid and client_id claims for the access token
|
||||
// See https://solid.github.io/authentication-panel/solid-oidc/#tokens-access
|
||||
config.extraAccessTokenClaims = (ctx, token): CanBePromise<AnyObject | void> =>
|
||||
// See https://solid.github.io/solid-oidc/#resource-access-validation
|
||||
config.extraTokenClaims = (ctx, token): CanBePromise<UnknownObject> =>
|
||||
this.isAccessToken(token) ?
|
||||
{ webid: token.accountId, client_id: token.clientId } :
|
||||
{ webid: token.accountId } :
|
||||
{};
|
||||
|
||||
config.features = {
|
||||
...config.features,
|
||||
resourceIndicators: {
|
||||
defaultResource(): string {
|
||||
// This value is irrelevant, but is necessary to trigger the `getResourceServerInfo` call below,
|
||||
// where it will be an input parameter in case the client provided no value.
|
||||
// Note that an empty string is not a valid value.
|
||||
return 'http://example.com/';
|
||||
},
|
||||
enabled: true,
|
||||
// This call is necessary to force the OIDC library to return a JWT access token.
|
||||
// See https://github.com/panva/node-oidc-provider/discussions/959#discussioncomment-524757
|
||||
getResourceServerInfo: (): ResourceServer => ({
|
||||
// The scopes of the Resource Server.
|
||||
// Since this is irrelevant at the moment, an empty string is fine.
|
||||
scope: '',
|
||||
audience: 'solid',
|
||||
accessTokenFormat: 'jwt',
|
||||
jwt: {
|
||||
sign: { alg: this.jwtAlg },
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -230,6 +267,7 @@ export class IdentityProviderFactory implements ProviderFactory {
|
||||
// When oidc-provider cannot fulfill the authorization request for any of the possible reasons
|
||||
// (missing user session, requested ACR not fulfilled, prompt requested, ...)
|
||||
// it will resolve the interactions.url helper function and redirect the User-Agent to that url.
|
||||
// Another requirement is that `features.userinfo` is disabled in the configuration.
|
||||
config.interactions = {
|
||||
url: async(ctx, oidcInteraction): Promise<string> => {
|
||||
const operation: Operation = {
|
||||
@@ -255,7 +293,7 @@ export class IdentityProviderFactory implements ProviderFactory {
|
||||
|
||||
config.routes = {
|
||||
authorization: this.createRoute('auth'),
|
||||
check_session: this.createRoute('session/check'),
|
||||
backchannel_authentication: this.createRoute('backchannel'),
|
||||
code_verification: this.createRoute('device'),
|
||||
device_authorization: this.createRoute('device/auth'),
|
||||
end_session: this.createRoute('session/end'),
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
|
||||
import { FoundHttpError } from '../../util/errors/FoundHttpError';
|
||||
import { BaseInteractionHandler } from './BaseInteractionHandler';
|
||||
import type { InteractionHandlerInput } from './InteractionHandler';
|
||||
import type { InteractionCompleterInput, InteractionCompleter } from './util/InteractionCompleter';
|
||||
|
||||
/**
|
||||
* Abstract extension of {@link BaseInteractionHandler} for handlers that need to call an {@link InteractionCompleter}.
|
||||
* This is required by handlers that handle IDP behaviour
|
||||
* and need to complete an OIDC interaction by redirecting back to the client,
|
||||
* such as when logging in.
|
||||
*
|
||||
* Calls the InteractionCompleter with the results returned by the helper function
|
||||
* and throw a corresponding {@link FoundHttpError}.
|
||||
*/
|
||||
export abstract class CompletingInteractionHandler extends BaseInteractionHandler {
|
||||
protected readonly interactionCompleter: InteractionCompleter;
|
||||
|
||||
protected constructor(view: Record<string, unknown>, interactionCompleter: InteractionCompleter) {
|
||||
super(view);
|
||||
this.interactionCompleter = interactionCompleter;
|
||||
}
|
||||
|
||||
public async canHandle(input: InteractionHandlerInput): Promise<void> {
|
||||
await super.canHandle(input);
|
||||
if (input.operation.method === 'POST' && !input.oidcInteraction) {
|
||||
throw new BadRequestHttpError(
|
||||
'This action can only be performed as part of an OIDC authentication flow.',
|
||||
{ errorCode: 'E0002' },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async handlePost(input: InteractionHandlerInput): Promise<never> {
|
||||
// Interaction is defined due to canHandle call
|
||||
const parameters = await this.getCompletionParameters(input as Required<InteractionHandlerInput>);
|
||||
const location = await this.interactionCompleter.handleSafe(parameters);
|
||||
throw new FoundHttpError(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the parameters necessary to call an InteractionCompleter.
|
||||
* The input parameters are the same that the `handlePost` function was called with.
|
||||
* @param input - The original input parameters to the `handle` function.
|
||||
*/
|
||||
protected abstract getCompletionParameters(input: Required<InteractionHandlerInput>):
|
||||
Promise<InteractionCompleterInput>;
|
||||
}
|
||||
111
src/identity/interaction/ConsentHandler.ts
Normal file
111
src/identity/interaction/ConsentHandler.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import type { InteractionResults, KoaContextWithOIDC, UnknownObject } from 'oidc-provider';
|
||||
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
|
||||
import { FoundHttpError } from '../../util/errors/FoundHttpError';
|
||||
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
|
||||
import { readJsonStream } from '../../util/StreamUtil';
|
||||
import type { ProviderFactory } from '../configuration/ProviderFactory';
|
||||
import { BaseInteractionHandler } from './BaseInteractionHandler';
|
||||
import type { Interaction, InteractionHandlerInput } from './InteractionHandler';
|
||||
|
||||
type Grant = NonNullable<KoaContextWithOIDC['oidc']['entities']['Grant']>;
|
||||
|
||||
/**
|
||||
* Handles the OIDC consent prompts where the user confirms they want to log in for the given client.
|
||||
*/
|
||||
export class ConsentHandler extends BaseInteractionHandler {
|
||||
private readonly providerFactory: ProviderFactory;
|
||||
|
||||
public constructor(providerFactory: ProviderFactory) {
|
||||
super({});
|
||||
this.providerFactory = providerFactory;
|
||||
}
|
||||
|
||||
public async canHandle(input: InteractionHandlerInput): Promise<void> {
|
||||
await super.canHandle(input);
|
||||
if (input.operation.method === 'POST' && !input.oidcInteraction) {
|
||||
throw new BadRequestHttpError(
|
||||
'This action can only be performed as part of an OIDC authentication flow.',
|
||||
{ errorCode: 'E0002' },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected async handlePost({ operation, oidcInteraction }: InteractionHandlerInput): Promise<never> {
|
||||
const { remember } = await readJsonStream(operation.body.data);
|
||||
|
||||
const grant = await this.getGrant(oidcInteraction!);
|
||||
this.updateGrant(grant, oidcInteraction!.prompt.details, remember);
|
||||
|
||||
const location = await this.updateInteraction(oidcInteraction!, grant);
|
||||
|
||||
throw new FoundHttpError(location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Either returns the grant associated with the given interaction or creates a new one if it does not exist yet.
|
||||
*/
|
||||
private async getGrant(oidcInteraction: Interaction): Promise<Grant> {
|
||||
if (!oidcInteraction.session) {
|
||||
throw new NotImplementedHttpError('Only interactions with a valid session are supported.');
|
||||
}
|
||||
|
||||
const { params, session: { accountId }, grantId } = oidcInteraction;
|
||||
const provider = await this.providerFactory.getProvider();
|
||||
let grant: Grant;
|
||||
if (grantId) {
|
||||
grant = (await provider.Grant.find(grantId))!;
|
||||
} else {
|
||||
grant = new provider.Grant({
|
||||
accountId,
|
||||
clientId: params.client_id as string,
|
||||
});
|
||||
}
|
||||
return grant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the grant with all the missing scopes and claims requested by the interaction.
|
||||
*
|
||||
* Will reject the `offline_access` scope if `remember` is false.
|
||||
*/
|
||||
private updateGrant(grant: Grant, details: UnknownObject, remember: boolean): void {
|
||||
// Reject the offline_access scope if the user does not want to be remembered
|
||||
if (!remember) {
|
||||
grant.rejectOIDCScope('offline_access');
|
||||
}
|
||||
|
||||
// Grant all the requested scopes and claims
|
||||
if (details.missingOIDCScope) {
|
||||
grant.addOIDCScope((details.missingOIDCScope as string[]).join(' '));
|
||||
}
|
||||
if (details.missingOIDCClaims) {
|
||||
grant.addOIDCClaims(details.missingOIDCClaims as string[]);
|
||||
}
|
||||
if (details.missingResourceScopes) {
|
||||
for (const [ indicator, scopes ] of Object.entries(details.missingResourceScopes as Record<string, string[]>)) {
|
||||
grant.addResourceScope(indicator, scopes.join(' '));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the interaction with the new grant and returns the resulting redirect URL.
|
||||
*/
|
||||
private async updateInteraction(oidcInteraction: Interaction, grant: Grant): Promise<string> {
|
||||
const grantId = await grant.save();
|
||||
|
||||
const consent: InteractionResults['consent'] = {};
|
||||
// Only need to update the grantId if it is new
|
||||
if (!oidcInteraction.grantId) {
|
||||
consent.grantId = grantId;
|
||||
}
|
||||
|
||||
const result: InteractionResults = { consent };
|
||||
|
||||
// Need to merge with previous submission
|
||||
oidcInteraction.result = { ...oidcInteraction.lastSubmission, ...result };
|
||||
await oidcInteraction.save(oidcInteraction.exp - Math.floor(Date.now() / 1000));
|
||||
|
||||
return oidcInteraction.returnTo;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
|
||||
import { readJsonStream } from '../../util/StreamUtil';
|
||||
import { CompletingInteractionHandler } from './CompletingInteractionHandler';
|
||||
import type { InteractionHandlerInput } from './InteractionHandler';
|
||||
import type { InteractionCompleter, InteractionCompleterInput } from './util/InteractionCompleter';
|
||||
|
||||
/**
|
||||
* Simple CompletingInteractionRoute that returns the session accountId as webId.
|
||||
* This is relevant when a client already logged in this session and tries logging in again.
|
||||
*/
|
||||
export class ExistingLoginHandler extends CompletingInteractionHandler {
|
||||
public constructor(interactionCompleter: InteractionCompleter) {
|
||||
super({}, interactionCompleter);
|
||||
}
|
||||
|
||||
protected async getCompletionParameters({ operation, oidcInteraction }: Required<InteractionHandlerInput>):
|
||||
Promise<InteractionCompleterInput> {
|
||||
if (!oidcInteraction.session) {
|
||||
throw new NotImplementedHttpError('Only interactions with a valid session are supported.');
|
||||
}
|
||||
|
||||
const { remember } = await readJsonStream(operation.body.data);
|
||||
return { oidcInteraction, webId: oidcInteraction.session.accountId, shouldRemember: Boolean(remember) };
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,8 @@ import type { TemplateEngine } from '../../../../util/templates/TemplateEngine';
|
||||
import { BaseInteractionHandler } from '../../BaseInteractionHandler';
|
||||
import type { InteractionHandlerInput } from '../../InteractionHandler';
|
||||
import type { InteractionRoute } from '../../routing/InteractionRoute';
|
||||
import type { EmailSender } from '../../util/EmailSender';
|
||||
import type { AccountStore } from '../storage/AccountStore';
|
||||
import type { EmailSender } from '../util/EmailSender';
|
||||
|
||||
const forgotPasswordView = {
|
||||
required: {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import assert from 'assert';
|
||||
import type { InteractionResults } from 'oidc-provider';
|
||||
import type { Operation } from '../../../../http/Operation';
|
||||
import { getLoggerFor } from '../../../../logging/LogUtil';
|
||||
import { BadRequestHttpError } from '../../../../util/errors/BadRequestHttpError';
|
||||
import { FoundHttpError } from '../../../../util/errors/FoundHttpError';
|
||||
import { readJsonStream } from '../../../../util/StreamUtil';
|
||||
import { CompletingInteractionHandler } from '../../CompletingInteractionHandler';
|
||||
import { BaseInteractionHandler } from '../../BaseInteractionHandler';
|
||||
import type { InteractionHandlerInput } from '../../InteractionHandler';
|
||||
import type { InteractionCompleterInput, InteractionCompleter } from '../../util/InteractionCompleter';
|
||||
import type { AccountStore } from '../storage/AccountStore';
|
||||
|
||||
const loginView = {
|
||||
@@ -26,19 +27,27 @@ interface LoginInput {
|
||||
* Handles the submission of the Login Form and logs the user in.
|
||||
* Will throw a RedirectHttpError on success.
|
||||
*/
|
||||
export class LoginHandler extends CompletingInteractionHandler {
|
||||
export class LoginHandler extends BaseInteractionHandler {
|
||||
protected readonly logger = getLoggerFor(this);
|
||||
|
||||
private readonly accountStore: AccountStore;
|
||||
|
||||
public constructor(accountStore: AccountStore, interactionCompleter: InteractionCompleter) {
|
||||
super(loginView, interactionCompleter);
|
||||
public constructor(accountStore: AccountStore) {
|
||||
super(loginView);
|
||||
this.accountStore = accountStore;
|
||||
}
|
||||
|
||||
protected async getCompletionParameters(input: Required<InteractionHandlerInput>):
|
||||
Promise<InteractionCompleterInput> {
|
||||
const { operation, oidcInteraction } = input;
|
||||
public async canHandle(input: InteractionHandlerInput): Promise<void> {
|
||||
await super.canHandle(input);
|
||||
if (input.operation.method === 'POST' && !input.oidcInteraction) {
|
||||
throw new BadRequestHttpError(
|
||||
'This action can only be performed as part of an OIDC authentication flow.',
|
||||
{ errorCode: 'E0002' },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async handlePost({ operation, oidcInteraction }: InteractionHandlerInput): Promise<never> {
|
||||
const { email, password, remember } = await this.parseInput(operation);
|
||||
// Try to log in, will error if email/password combination is invalid
|
||||
const webId = await this.accountStore.authenticate(email, password);
|
||||
@@ -49,7 +58,15 @@ export class LoginHandler extends CompletingInteractionHandler {
|
||||
}
|
||||
this.logger.debug(`Logging in user ${email}`);
|
||||
|
||||
return { oidcInteraction, webId, shouldRemember: remember };
|
||||
// Update the interaction to get the redirect URL
|
||||
const login: InteractionResults['login'] = {
|
||||
accountId: webId,
|
||||
remember,
|
||||
};
|
||||
oidcInteraction!.result = { login };
|
||||
await oidcInteraction!.save(oidcInteraction!.exp - Math.floor(Date.now() / 1000));
|
||||
|
||||
throw new FoundHttpError(oidcInteraction!.returnTo);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AsyncHandler } from '../../../util/handlers/AsyncHandler';
|
||||
import { AsyncHandler } from '../../../../util/handlers/AsyncHandler';
|
||||
|
||||
export interface EmailArgs {
|
||||
recipient: string;
|
||||
@@ -1,37 +0,0 @@
|
||||
import type { InteractionResults } from 'oidc-provider';
|
||||
import type { InteractionCompleterInput } from './InteractionCompleter';
|
||||
import { InteractionCompleter } from './InteractionCompleter';
|
||||
|
||||
/**
|
||||
* Creates a simple InteractionResults object based on the input parameters and injects it in the Interaction.
|
||||
*/
|
||||
export class BaseInteractionCompleter extends InteractionCompleter {
|
||||
public async handle(input: InteractionCompleterInput): Promise<string> {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const result: InteractionResults = {
|
||||
login: {
|
||||
account: input.webId,
|
||||
// Indicates if a persistent cookie should be used instead of a session cookie.
|
||||
remember: input.shouldRemember,
|
||||
ts: now,
|
||||
},
|
||||
consent: {
|
||||
// When OIDC clients want a refresh token, they need to request the 'offline_access' scope.
|
||||
// This indicates that this scope is not granted to the client in case they do not want to be remembered.
|
||||
rejectedScopes: input.shouldRemember ? [] : [ 'offline_access' ],
|
||||
},
|
||||
};
|
||||
|
||||
// Generates the URL a client needs to be redirected to
|
||||
// after a successful interaction completion (such as logging in).
|
||||
// Identical behaviour to calling `provider.interactionResult`.
|
||||
// We use the code below instead of calling that function
|
||||
// since that function also uses Request/Response objects to generate the Interaction object,
|
||||
// which we already have here.
|
||||
const { oidcInteraction } = input;
|
||||
oidcInteraction.result = { ...oidcInteraction.lastSubmission, ...result };
|
||||
await oidcInteraction.save(oidcInteraction.exp - now);
|
||||
|
||||
return oidcInteraction.returnTo;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { AsyncHandler } from '../../../util/handlers/AsyncHandler';
|
||||
import type { Interaction } from '../InteractionHandler';
|
||||
|
||||
/**
|
||||
* Parameters required to specify how the interaction should be completed.
|
||||
*/
|
||||
export interface InteractionCompleterInput {
|
||||
oidcInteraction: Interaction;
|
||||
webId: string;
|
||||
shouldRemember?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class responsible for completing the interaction based on the parameters provided.
|
||||
*/
|
||||
export abstract class InteractionCompleter extends AsyncHandler<InteractionCompleterInput, string> {}
|
||||
11
src/index.ts
11
src/index.ts
@@ -139,6 +139,8 @@ export * from './identity/interaction/email-password/storage/AccountStore';
|
||||
export * from './identity/interaction/email-password/storage/BaseAccountStore';
|
||||
|
||||
// Identity/Interaction/Email-Password/Util
|
||||
export * from './identity/interaction/email-password/util/BaseEmailSender';
|
||||
export * from './identity/interaction/email-password/util/EmailSender';
|
||||
export * from './identity/interaction/email-password/util/RegistrationManager';
|
||||
|
||||
// Identity/Interaction/Email-Password
|
||||
@@ -150,16 +152,9 @@ export * from './identity/interaction/routing/InteractionRoute';
|
||||
export * from './identity/interaction/routing/InteractionRouteHandler';
|
||||
export * from './identity/interaction/routing/RelativePathInteractionRoute';
|
||||
|
||||
// Identity/Interaction/Util
|
||||
export * from './identity/interaction/util/BaseEmailSender';
|
||||
export * from './identity/interaction/util/BaseInteractionCompleter';
|
||||
export * from './identity/interaction/util/EmailSender';
|
||||
export * from './identity/interaction/util/InteractionCompleter';
|
||||
|
||||
// Identity/Interaction
|
||||
export * from './identity/interaction/BaseInteractionHandler';
|
||||
export * from './identity/interaction/CompletingInteractionHandler';
|
||||
export * from './identity/interaction/ExistingLoginHandler';
|
||||
export * from './identity/interaction/ConsentHandler';
|
||||
export * from './identity/interaction/ControlHandler';
|
||||
export * from './identity/interaction/FixedInteractionHandler';
|
||||
export * from './identity/interaction/HtmlViewHandler';
|
||||
|
||||
Reference in New Issue
Block a user