mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-06-06 09:31:35 -04:00
fix(auth): guest login fixes, more reliable pending user session handler
Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
parent
167135a8d0
commit
3a90c9ca96
9 changed files with 147 additions and 97 deletions
|
@ -68,14 +68,10 @@ export class AuthController {
|
||||||
getPendingUserData(
|
getPendingUserData(
|
||||||
@Req() request: RequestWithSession,
|
@Req() request: RequestWithSession,
|
||||||
): Partial<PendingUserInfoDto> {
|
): Partial<PendingUserInfoDto> {
|
||||||
if (
|
if (!request.session.pendingUser?.confirmationData) {
|
||||||
!request.session.newUserData ||
|
|
||||||
!request.session.authProviderIdentifier ||
|
|
||||||
!request.session.authProviderType
|
|
||||||
) {
|
|
||||||
throw new BadRequestException('No pending user data');
|
throw new BadRequestException('No pending user data');
|
||||||
}
|
}
|
||||||
return request.session.newUserData;
|
return request.session.pendingUser.confirmationData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put('pending-user')
|
@Put('pending-user')
|
||||||
|
@ -85,32 +81,33 @@ export class AuthController {
|
||||||
@Body() pendingUserConfirmationData: PendingUserConfirmationDto,
|
@Body() pendingUserConfirmationData: PendingUserConfirmationDto,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (
|
if (
|
||||||
!request.session.newUserData ||
|
!request.session.pendingUser?.confirmationData ||
|
||||||
!request.session.authProviderIdentifier ||
|
!request.session.pendingUser?.authProviderType ||
|
||||||
!request.session.authProviderType ||
|
!request.session.pendingUser?.authProviderIdentifier ||
|
||||||
!request.session.providerUserId
|
!request.session.pendingUser?.providerUserId
|
||||||
) {
|
) {
|
||||||
throw new BadRequestException('No pending user data');
|
throw new BadRequestException('No pending user data');
|
||||||
}
|
}
|
||||||
request.session.userId =
|
request.session.userId =
|
||||||
await this.identityService.createUserWithIdentityFromPendingUserConfirmation(
|
await this.identityService.createUserWithIdentityFromPendingUserConfirmation(
|
||||||
request.session.newUserData,
|
request.session.pendingUser.confirmationData,
|
||||||
pendingUserConfirmationData,
|
pendingUserConfirmationData,
|
||||||
request.session.authProviderType,
|
request.session.pendingUser.authProviderType,
|
||||||
request.session.authProviderIdentifier,
|
request.session.pendingUser.authProviderIdentifier,
|
||||||
request.session.providerUserId,
|
request.session.pendingUser.providerUserId,
|
||||||
);
|
);
|
||||||
|
request.session.authProviderType =
|
||||||
|
request.session.pendingUser.authProviderType;
|
||||||
|
request.session.authProviderIdentifier =
|
||||||
|
request.session.pendingUser.authProviderIdentifier;
|
||||||
// Cleanup
|
// Cleanup
|
||||||
request.session.newUserData = undefined;
|
request.session.pendingUser = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete('pending-user')
|
@Delete('pending-user')
|
||||||
@OpenApi(204, 400)
|
@OpenApi(204, 400)
|
||||||
deletePendingUserData(@Req() request: RequestWithSession): void {
|
deletePendingUserData(@Req() request: RequestWithSession): void {
|
||||||
request.session.newUserData = undefined;
|
request.session.pendingUser = undefined;
|
||||||
request.session.authProviderIdentifier = undefined;
|
request.session.oidc = undefined;
|
||||||
request.session.authProviderType = undefined;
|
|
||||||
request.session.providerUserId = undefined;
|
|
||||||
request.session.oidcIdToken = undefined;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,9 +54,6 @@ export class LdapController {
|
||||||
loginDto.password,
|
loginDto.password,
|
||||||
);
|
);
|
||||||
try {
|
try {
|
||||||
request.session.authProviderType = AuthProviderType.LDAP;
|
|
||||||
request.session.authProviderIdentifier = ldapIdentifier;
|
|
||||||
request.session.providerUserId = userInfo.id;
|
|
||||||
const identity =
|
const identity =
|
||||||
await this.identityService.getIdentityFromUserIdAndProviderType(
|
await this.identityService.getIdentityFromUserIdAndProviderType(
|
||||||
userInfo.id,
|
userInfo.id,
|
||||||
|
@ -71,11 +68,18 @@ export class LdapController {
|
||||||
userInfo.photoUrl,
|
userInfo.photoUrl,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
request.session.authProviderType = AuthProviderType.LDAP;
|
||||||
|
request.session.authProviderIdentifier = ldapIdentifier;
|
||||||
request.session.userId = identity[FieldNameIdentity.userId];
|
request.session.userId = identity[FieldNameIdentity.userId];
|
||||||
return { newUser: false };
|
return { newUser: false };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof NotInDBError) {
|
if (error instanceof NotInDBError) {
|
||||||
request.session.newUserData = userInfo;
|
request.session.pendingUser = {
|
||||||
|
authProviderType: AuthProviderType.LDAP,
|
||||||
|
authProviderIdentifier: ldapIdentifier,
|
||||||
|
confirmationData: userInfo,
|
||||||
|
providerUserId: userInfo.id,
|
||||||
|
};
|
||||||
return { newUser: true };
|
return { newUser: true };
|
||||||
}
|
}
|
||||||
this.logger.error(`Error during LDAP login: ${String(error)}`);
|
this.logger.error(`Error during LDAP login: ${String(error)}`);
|
||||||
|
|
|
@ -59,6 +59,7 @@ export class LocalController {
|
||||||
// Log the user in after registration
|
// Log the user in after registration
|
||||||
request.session.authProviderType = AuthProviderType.LOCAL;
|
request.session.authProviderType = AuthProviderType.LOCAL;
|
||||||
request.session.userId = userId;
|
request.session.userId = userId;
|
||||||
|
request.session.pendingUser = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(LoginEnabledGuard, SessionGuard)
|
@UseGuards(LoginEnabledGuard, SessionGuard)
|
||||||
|
@ -98,6 +99,7 @@ export class LocalController {
|
||||||
);
|
);
|
||||||
request.session.userId = identity[FieldNameIdentity.userId];
|
request.session.userId = identity[FieldNameIdentity.userId];
|
||||||
request.session.authProviderType = AuthProviderType.LOCAL;
|
request.session.authProviderType = AuthProviderType.LOCAL;
|
||||||
|
request.session.pendingUser = undefined;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.log(`Failed to log in user: ${String(error)}`, 'login');
|
this.logger.log(`Failed to log in user: ${String(error)}`, 'login');
|
||||||
throw new UnauthorizedException('Invalid username or password');
|
throw new UnauthorizedException('Invalid username or password');
|
||||||
|
|
|
@ -45,10 +45,14 @@ export class OidcController {
|
||||||
): { url: string } {
|
): { url: string } {
|
||||||
const code = this.oidcService.generateCode();
|
const code = this.oidcService.generateCode();
|
||||||
const state = this.oidcService.generateState();
|
const state = this.oidcService.generateState();
|
||||||
request.session.oidcLoginCode = code;
|
request.session.oidc = {
|
||||||
request.session.oidcLoginState = state;
|
loginCode: code,
|
||||||
request.session.authProviderType = AuthProviderType.OIDC;
|
loginState: state,
|
||||||
request.session.authProviderIdentifier = oidcIdentifier;
|
};
|
||||||
|
request.session.pendingUser = {
|
||||||
|
authProviderType: AuthProviderType.OIDC,
|
||||||
|
authProviderIdentifier: oidcIdentifier,
|
||||||
|
};
|
||||||
const authorizationUrl = this.oidcService.getAuthorizationUrl(
|
const authorizationUrl = this.oidcService.getAuthorizationUrl(
|
||||||
oidcIdentifier,
|
oidcIdentifier,
|
||||||
code,
|
code,
|
||||||
|
@ -69,12 +73,11 @@ export class OidcController {
|
||||||
oidcIdentifier,
|
oidcIdentifier,
|
||||||
request,
|
request,
|
||||||
);
|
);
|
||||||
const oidcUserIdentifier = request.session.providerUserId;
|
const oidcUserIdentifier = request.session.pendingUser?.providerUserId;
|
||||||
if (!oidcUserIdentifier) {
|
if (!oidcUserIdentifier) {
|
||||||
this.logger.log('No OIDC user identifier in callback', 'callback');
|
this.logger.log('No OIDC user identifier in callback', 'callback');
|
||||||
throw new UnauthorizedException('No OIDC user identifier found');
|
throw new UnauthorizedException('No OIDC user identifier found');
|
||||||
}
|
}
|
||||||
request.session.authProviderType = AuthProviderType.OIDC;
|
|
||||||
const identity = await this.oidcService.getExistingOidcIdentity(
|
const identity = await this.oidcService.getExistingOidcIdentity(
|
||||||
oidcIdentifier,
|
oidcIdentifier,
|
||||||
oidcUserIdentifier,
|
oidcUserIdentifier,
|
||||||
|
@ -82,7 +85,6 @@ export class OidcController {
|
||||||
const mayUpdate = this.identityService.mayUpdateIdentity(oidcIdentifier);
|
const mayUpdate = this.identityService.mayUpdateIdentity(oidcIdentifier);
|
||||||
|
|
||||||
if (identity === null) {
|
if (identity === null) {
|
||||||
request.session.newUserData = userInfo;
|
|
||||||
return { url: '/new-user' };
|
return { url: '/new-user' };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,6 +99,9 @@ export class OidcController {
|
||||||
}
|
}
|
||||||
|
|
||||||
request.session.userId = userId;
|
request.session.userId = userId;
|
||||||
|
request.session.authProviderType = AuthProviderType.OIDC;
|
||||||
|
request.session.authProviderIdentifier = oidcIdentifier;
|
||||||
|
request.session.pendingUser = undefined;
|
||||||
return { url: '/' };
|
return { url: '/' };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof HttpException) {
|
if (error instanceof HttpException) {
|
||||||
|
|
|
@ -69,16 +69,18 @@ export class OidcService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @async
|
* Fetches the client and its config (issuer, metadata) for the given OIDC configuration
|
||||||
* Fetches the client and its config (issuer, metadata) for the given OIDC configuration.
|
|
||||||
*
|
*
|
||||||
* @param {OidcConfig} oidcConfig The OIDC configuration to fetch the client config for
|
* @param oidcConfig The OIDC configuration to fetch the client config for
|
||||||
* @returns {OidcClientConfigEntry} A promise that resolves to the client configuration.
|
* @returns A promise that resolves to the client configuration.
|
||||||
*/
|
*/
|
||||||
private async fetchClientConfig(
|
private async fetchClientConfig(
|
||||||
oidcConfig: OidcConfig,
|
oidcConfig: OidcConfig,
|
||||||
): Promise<OidcClientConfigEntry> {
|
): Promise<OidcClientConfigEntry> {
|
||||||
const useAutodiscover = oidcConfig.authorizeUrl === undefined;
|
const useAutodiscover =
|
||||||
|
oidcConfig.authorizeUrl === undefined ||
|
||||||
|
oidcConfig.tokenUrl === undefined ||
|
||||||
|
oidcConfig.userinfoUrl === undefined;
|
||||||
const issuer = useAutodiscover
|
const issuer = useAutodiscover
|
||||||
? await Issuer.discover(oidcConfig.issuer)
|
? await Issuer.discover(oidcConfig.issuer)
|
||||||
: new Issuer({
|
: new Issuer({
|
||||||
|
@ -117,7 +119,7 @@ export class OidcService {
|
||||||
/**
|
/**
|
||||||
* Generates a secure code verifier for the OIDC login.
|
* Generates a secure code verifier for the OIDC login.
|
||||||
*
|
*
|
||||||
* @returns {string} The generated code verifier.
|
* @returns The generated code verifier.
|
||||||
*/
|
*/
|
||||||
generateCode(): string {
|
generateCode(): string {
|
||||||
return generators.codeVerifier();
|
return generators.codeVerifier();
|
||||||
|
@ -126,7 +128,7 @@ export class OidcService {
|
||||||
/**
|
/**
|
||||||
* Generates a random state for the OIDC login.
|
* Generates a random state for the OIDC login.
|
||||||
*
|
*
|
||||||
* @returns {string} The generated state.
|
* @returns The generated state.
|
||||||
*/
|
*/
|
||||||
generateState(): string {
|
generateState(): string {
|
||||||
return generators.state();
|
return generators.state();
|
||||||
|
@ -135,10 +137,10 @@ export class OidcService {
|
||||||
/**
|
/**
|
||||||
* Generates the authorization URL for the given OIDC identifier and code.
|
* Generates the authorization URL for the given OIDC identifier and code.
|
||||||
*
|
*
|
||||||
* @param {string} oidcIdentifier The identifier of the OIDC configuration
|
* @param oidcIdentifier The identifier of the OIDC configuration
|
||||||
* @param {string} code The code verifier generated for the login
|
* @param code The code verifier generated for the login
|
||||||
* @param {string} state The state generated for the login
|
* @param state The state generated for the login
|
||||||
* @returns {string} The generated authorization URL
|
* @returns The generated authorization URL
|
||||||
*/
|
*/
|
||||||
getAuthorizationUrl(
|
getAuthorizationUrl(
|
||||||
oidcIdentifier: string,
|
oidcIdentifier: string,
|
||||||
|
@ -163,7 +165,6 @@ export class OidcService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @async
|
|
||||||
* Extracts the user information from the callback and stores them in the session.
|
* Extracts the user information from the callback and stores them in the session.
|
||||||
* Afterward, the user information is returned.
|
* Afterward, the user information is returned.
|
||||||
*
|
*
|
||||||
|
@ -184,8 +185,8 @@ export class OidcService {
|
||||||
const client = clientConfig.client;
|
const client = clientConfig.client;
|
||||||
const oidcConfig = clientConfig.config;
|
const oidcConfig = clientConfig.config;
|
||||||
const params = client.callbackParams(request);
|
const params = client.callbackParams(request);
|
||||||
const code = request.session.oidcLoginCode;
|
const code = request.session.oidc?.loginCode;
|
||||||
const state = request.session.oidcLoginState;
|
const state = request.session.oidc?.loginState;
|
||||||
const isAutodiscovered = clientConfig.config.authorizeUrl === undefined;
|
const isAutodiscovered = clientConfig.config.authorizeUrl === undefined;
|
||||||
const callbackMethod = isAutodiscovered
|
const callbackMethod = isAutodiscovered
|
||||||
? client.callback.bind(client)
|
? client.callback.bind(client)
|
||||||
|
@ -196,7 +197,9 @@ export class OidcService {
|
||||||
state,
|
state,
|
||||||
});
|
});
|
||||||
|
|
||||||
request.session.oidcIdToken = tokenSet.id_token;
|
request.session.oidc = {
|
||||||
|
idToken: tokenSet.id_token,
|
||||||
|
};
|
||||||
const userInfoResponse = await client.userinfo(tokenSet);
|
const userInfoResponse = await client.userinfo(tokenSet);
|
||||||
const userId = OidcService.getResponseFieldValue(
|
const userId = OidcService.getResponseFieldValue(
|
||||||
userInfoResponse,
|
userInfoResponse,
|
||||||
|
@ -229,22 +232,21 @@ export class OidcService {
|
||||||
photoUrl: photoUrl ?? null,
|
photoUrl: photoUrl ?? null,
|
||||||
email: email ?? null,
|
email: email ?? null,
|
||||||
};
|
};
|
||||||
request.session.providerUserId = userId;
|
request.session.pendingUser = {
|
||||||
request.session.newUserData = newUserData;
|
authProviderType: AuthProviderType.OIDC,
|
||||||
// Cleanup: The code isn't necessary anymore
|
authProviderIdentifier: oidcIdentifier,
|
||||||
request.session.oidcLoginCode = undefined;
|
providerUserId: userId,
|
||||||
request.session.oidcLoginState = undefined;
|
confirmationData: newUserData,
|
||||||
|
};
|
||||||
return newUserData;
|
return newUserData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @async
|
* Checks if an identity exists for a given OIDC user and returns it if it does
|
||||||
* Checks if an identity exists for a given OIDC user and returns it if it does.
|
|
||||||
*
|
*
|
||||||
* @param {string} oidcIdentifier The identifier of the OIDC configuration
|
* @param oidcIdentifier The identifier of the OIDC configuration
|
||||||
* @param {string} oidcUserId The id of the user in the OIDC system
|
* @param oidcUserId The id of the user in the OIDC system
|
||||||
* @returns {Identity} The identity if it exists
|
* @returns The identity if it exists, null otherwise
|
||||||
* @returns {null} when the identity does not exist
|
|
||||||
*/
|
*/
|
||||||
async getExistingOidcIdentity(
|
async getExistingOidcIdentity(
|
||||||
oidcIdentifier: string,
|
oidcIdentifier: string,
|
||||||
|
@ -277,11 +279,10 @@ export class OidcService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the logout URL for the given request if the user is logged in with OIDC.
|
* Returns the logout URL for the given request if the user is logged in with OIDC
|
||||||
*
|
*
|
||||||
* @param {RequestWithSession} request The request containing the session
|
* @param request The request containing the session
|
||||||
* @returns {string} The logout URL if the user is logged in with OIDC
|
* @returns The logout URL if the user is logged in with OIDC, or null if there is no URL to redirect to
|
||||||
* @returns {null} when there is no logout URL to redirect to
|
|
||||||
*/
|
*/
|
||||||
getLogoutUrl(request: RequestWithSession): string | null {
|
getLogoutUrl(request: RequestWithSession): string | null {
|
||||||
const oidcIdentifier = request.session.authProviderIdentifier;
|
const oidcIdentifier = request.session.authProviderIdentifier;
|
||||||
|
@ -296,7 +297,7 @@ export class OidcService {
|
||||||
}
|
}
|
||||||
const issuer = clientConfig.issuer;
|
const issuer = clientConfig.issuer;
|
||||||
const endSessionEndpoint = issuer.metadata.end_session_endpoint;
|
const endSessionEndpoint = issuer.metadata.end_session_endpoint;
|
||||||
const idToken = request.session.oidcIdToken;
|
const idToken = request.session.oidc?.idToken;
|
||||||
if (!endSessionEndpoint) {
|
if (!endSessionEndpoint) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -304,12 +305,12 @@ export class OidcService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a specific field from the userinfo object or a default value.
|
* Returns a specific field from the userinfo object or a default value
|
||||||
*
|
*
|
||||||
* @param {UserinfoResponse} response The response from the OIDC userinfo endpoint
|
* @param response The response from the OIDC userinfo endpoint
|
||||||
* @param {string} field The field to get from the response
|
* @param field The field to get from the response
|
||||||
* @param {string|undefined} defaultValue The default value to return if the value is empty
|
* @param defaultValue The default value to return if the value is empty
|
||||||
* @returns {string|undefined} The value of the field from the response or the default value
|
* @returns The value of the field from the response or the default value
|
||||||
*/
|
*/
|
||||||
private static getResponseFieldValue<T extends string | undefined>(
|
private static getResponseFieldValue<T extends string | undefined>(
|
||||||
response: UserinfoResponse,
|
response: UserinfoResponse,
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
const {
|
const {
|
||||||
AuthProviderType,
|
AuthProviderType,
|
||||||
|
@ -83,7 +89,8 @@ const up = async function (knex) {
|
||||||
.unsigned()
|
.unsigned()
|
||||||
.notNullable()
|
.notNullable()
|
||||||
.references(FieldNameUser.id)
|
.references(FieldNameUser.id)
|
||||||
.inTable(TableUser);
|
.inTable(TableUser)
|
||||||
|
.onDelete('CASCADE');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create aliases table
|
// Create aliases table
|
||||||
|
@ -97,7 +104,8 @@ const up = async function (knex) {
|
||||||
.unsigned()
|
.unsigned()
|
||||||
.notNullable()
|
.notNullable()
|
||||||
.references(FieldNameNote.id)
|
.references(FieldNameNote.id)
|
||||||
.inTable(TableNote);
|
.inTable(TableNote)
|
||||||
|
.onDelete('CASCADE');
|
||||||
table.boolean(FieldNameAlias.isPrimary).nullable();
|
table.boolean(FieldNameAlias.isPrimary).nullable();
|
||||||
table.unique([FieldNameAlias.noteId, FieldNameAlias.isPrimary], {
|
table.unique([FieldNameAlias.noteId, FieldNameAlias.isPrimary], {
|
||||||
indexName: 'only_one_note_can_be_primary',
|
indexName: 'only_one_note_can_be_primary',
|
||||||
|
@ -113,7 +121,8 @@ const up = async function (knex) {
|
||||||
.unsigned()
|
.unsigned()
|
||||||
.notNullable()
|
.notNullable()
|
||||||
.references(FieldNameUser.id)
|
.references(FieldNameUser.id)
|
||||||
.inTable(TableUser);
|
.inTable(TableUser)
|
||||||
|
.onDelete('CASCADE');
|
||||||
table.string(FieldNameApiToken.label).notNullable();
|
table.string(FieldNameApiToken.label).notNullable();
|
||||||
table.string(FieldNameApiToken.secretHash).notNullable();
|
table.string(FieldNameApiToken.secretHash).notNullable();
|
||||||
table
|
table
|
||||||
|
@ -130,7 +139,8 @@ const up = async function (knex) {
|
||||||
.unsigned()
|
.unsigned()
|
||||||
.notNullable()
|
.notNullable()
|
||||||
.references(FieldNameUser.id)
|
.references(FieldNameUser.id)
|
||||||
.inTable(TableUser);
|
.inTable(TableUser)
|
||||||
|
.onDelete('CASCADE');
|
||||||
table.enu(
|
table.enu(
|
||||||
FieldNameIdentity.providerType,
|
FieldNameIdentity.providerType,
|
||||||
[AuthProviderType.LDAP, AuthProviderType.LOCAL, AuthProviderType.OIDC], // AuthProviderType.GUEST is not relevant for the DB
|
[AuthProviderType.LDAP, AuthProviderType.LOCAL, AuthProviderType.OIDC], // AuthProviderType.GUEST is not relevant for the DB
|
||||||
|
@ -168,13 +178,15 @@ const up = async function (knex) {
|
||||||
.unsigned()
|
.unsigned()
|
||||||
.notNullable()
|
.notNullable()
|
||||||
.references(FieldNameUser.id)
|
.references(FieldNameUser.id)
|
||||||
.inTable(TableUser);
|
.inTable(TableUser)
|
||||||
|
.onDelete('CASCADE');
|
||||||
table
|
table
|
||||||
.integer(FieldNameGroupUser.groupId)
|
.integer(FieldNameGroupUser.groupId)
|
||||||
.unsigned()
|
.unsigned()
|
||||||
.notNullable()
|
.notNullable()
|
||||||
.references(FieldNameGroup.id)
|
.references(FieldNameGroup.id)
|
||||||
.inTable(TableGroup);
|
.inTable(TableGroup)
|
||||||
|
.onDelete('CASCADE');
|
||||||
table.primary([FieldNameGroupUser.userId, FieldNameGroupUser.groupId]);
|
table.primary([FieldNameGroupUser.userId, FieldNameGroupUser.groupId]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -186,7 +198,8 @@ const up = async function (knex) {
|
||||||
.unsigned()
|
.unsigned()
|
||||||
.notNullable()
|
.notNullable()
|
||||||
.references(FieldNameNote.id)
|
.references(FieldNameNote.id)
|
||||||
.inTable(TableNote);
|
.inTable(TableNote)
|
||||||
|
.onDelete('CASCADE');
|
||||||
table.text(FieldNameRevision.patch).notNullable();
|
table.text(FieldNameRevision.patch).notNullable();
|
||||||
table.text(FieldNameRevision.content).notNullable();
|
table.text(FieldNameRevision.content).notNullable();
|
||||||
table.string(FieldNameRevision.title).notNullable();
|
table.string(FieldNameRevision.title).notNullable();
|
||||||
|
@ -303,7 +316,8 @@ const up = async function (knex) {
|
||||||
.unsigned()
|
.unsigned()
|
||||||
.nullable()
|
.nullable()
|
||||||
.references(FieldNameNote.id)
|
.references(FieldNameNote.id)
|
||||||
.inTable(TableNote);
|
.inTable(TableNote)
|
||||||
|
.onDelete('SET NULL');
|
||||||
table
|
table
|
||||||
.integer(FieldNameMediaUpload.userId)
|
.integer(FieldNameMediaUpload.userId)
|
||||||
.unsigned()
|
.unsigned()
|
||||||
|
@ -340,13 +354,15 @@ const up = async function (knex) {
|
||||||
.unsigned()
|
.unsigned()
|
||||||
.notNullable()
|
.notNullable()
|
||||||
.references(FieldNameUser.id)
|
.references(FieldNameUser.id)
|
||||||
.inTable(TableUser);
|
.inTable(TableUser)
|
||||||
|
.onDelete('CASCADE');
|
||||||
table
|
table
|
||||||
.integer(FieldNameUserPinnedNote.noteId)
|
.integer(FieldNameUserPinnedNote.noteId)
|
||||||
.unsigned()
|
.unsigned()
|
||||||
.notNullable()
|
.notNullable()
|
||||||
.references(FieldNameNote.id)
|
.references(FieldNameNote.id)
|
||||||
.inTable(TableNote);
|
.inTable(TableNote)
|
||||||
|
.onDelete('CASCADE');
|
||||||
table.primary([
|
table.primary([
|
||||||
FieldNameUserPinnedNote.userId,
|
FieldNameUserPinnedNote.userId,
|
||||||
FieldNameUserPinnedNote.noteId,
|
FieldNameUserPinnedNote.noteId,
|
||||||
|
|
|
@ -7,6 +7,31 @@ import { AuthProviderType, PendingUserInfoDto } from '@hedgedoc/commons';
|
||||||
import { FieldNameUser, User } from '@hedgedoc/database';
|
import { FieldNameUser, User } from '@hedgedoc/database';
|
||||||
import { Cookie } from 'express-session';
|
import { Cookie } from 'express-session';
|
||||||
|
|
||||||
|
interface OidcAuthSessionState {
|
||||||
|
/** The id token to identify a user session with an OIDC auth provider, required for the logout */
|
||||||
|
idToken?: string;
|
||||||
|
|
||||||
|
/** The (random) OIDC code for verifying that OIDC responses match the OIDC requests */
|
||||||
|
loginCode?: string;
|
||||||
|
|
||||||
|
/** The (random) OIDC state for verifying that OIDC responses match the OIDC requests */
|
||||||
|
loginState?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PendingUserSessionState {
|
||||||
|
/** The pending user confirmation data */
|
||||||
|
confirmationData?: PendingUserInfoDto;
|
||||||
|
|
||||||
|
/** The pending user auth provider type */
|
||||||
|
authProviderType?: AuthProviderType;
|
||||||
|
|
||||||
|
/** The pending user auth provider identifier */
|
||||||
|
authProviderIdentifier?: string;
|
||||||
|
|
||||||
|
/** The pending user id as provided from the external auth provider, required for matching to a HedgeDoc identity */
|
||||||
|
providerUserId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SessionState {
|
export interface SessionState {
|
||||||
/** Details about the currently used session cookie */
|
/** Details about the currently used session cookie */
|
||||||
cookie: Cookie;
|
cookie: Cookie;
|
||||||
|
@ -14,24 +39,15 @@ export interface SessionState {
|
||||||
/** Contains the username if logged in completely, is undefined when not being logged in */
|
/** Contains the username if logged in completely, is undefined when not being logged in */
|
||||||
userId?: User[FieldNameUser.id];
|
userId?: User[FieldNameUser.id];
|
||||||
|
|
||||||
/** The auth provider that is used for the current login or pending login */
|
/** The auth provider that is used for the current login */
|
||||||
authProviderType?: AuthProviderType;
|
authProviderType?: AuthProviderType;
|
||||||
|
|
||||||
/** The identifier of the auth provider that is used for the current login or pending login */
|
/** The identifier of the auth provider that is used for the current login */
|
||||||
authProviderIdentifier?: string;
|
authProviderIdentifier?: string;
|
||||||
|
|
||||||
/** The id token to identify a user session with an OIDC auth provider, required for the logout */
|
/** Session data used on OIDC login */
|
||||||
oidcIdToken?: string;
|
oidc?: OidcAuthSessionState;
|
||||||
|
|
||||||
/** The (random) OIDC code for verifying that OIDC responses match the OIDC requests */
|
|
||||||
oidcLoginCode?: string;
|
|
||||||
|
|
||||||
/** The (random) OIDC state for verifying that OIDC responses match the OIDC requests */
|
|
||||||
oidcLoginState?: string;
|
|
||||||
|
|
||||||
/** The user id as provided from the external auth provider, required for matching to a HedgeDoc identity */
|
|
||||||
providerUserId?: string;
|
|
||||||
|
|
||||||
/** The user data of the user that is currently being created */
|
/** The user data of the user that is currently being created */
|
||||||
newUserData?: PendingUserInfoDto;
|
pendingUser?: PendingUserSessionState;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
*/
|
*/
|
||||||
import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-api-request-builder'
|
import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-api-request-builder'
|
||||||
import { GetApiRequestBuilder } from '../common/api-request-builder/get-api-request-builder'
|
import { GetApiRequestBuilder } from '../common/api-request-builder/get-api-request-builder'
|
||||||
import { PostApiRequestBuilder } from '../common/api-request-builder/post-api-request-builder'
|
|
||||||
import type { UpdateUserInfoDto, LoginUserInfoDto, MediaUploadDto } from '@hedgedoc/commons'
|
import type { UpdateUserInfoDto, LoginUserInfoDto, MediaUploadDto } from '@hedgedoc/commons'
|
||||||
|
import { PutApiRequestBuilder } from '../common/api-request-builder/put-api-request-builder'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns metadata about the currently signed-in user from the API.
|
* Returns metadata about the currently signed-in user from the API.
|
||||||
|
@ -36,7 +36,7 @@ export const deleteUser = async (): Promise<void> => {
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const updateUser = async (displayName: string | null, email: string | null): Promise<void> => {
|
export const updateUser = async (displayName: string | null, email: string | null): Promise<void> => {
|
||||||
await new PostApiRequestBuilder<void, UpdateUserInfoDto>('me/profile')
|
await new PutApiRequestBuilder<void, UpdateUserInfoDto>('me/profile')
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
displayName,
|
displayName,
|
||||||
email
|
email
|
||||||
|
|
|
@ -7,6 +7,9 @@
|
||||||
import { logInGuest, registerGuest } from '../../../api/auth/guest'
|
import { logInGuest, registerGuest } from '../../../api/auth/guest'
|
||||||
import { store } from '../../../redux'
|
import { store } from '../../../redux'
|
||||||
import { fetchAndSetUser } from '../../login-page/utils/fetch-and-set-user'
|
import { fetchAndSetUser } from '../../login-page/utils/fetch-and-set-user'
|
||||||
|
import { Logger } from '../../../utils/logger'
|
||||||
|
|
||||||
|
const logger = new Logger('LoginOrRegisterGuest')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the auth process towards the backend for guests
|
* Handles the auth process towards the backend for guests
|
||||||
|
@ -14,18 +17,24 @@ import { fetchAndSetUser } from '../../login-page/utils/fetch-and-set-user'
|
||||||
* If there is a guest uuid in local storage, the guest with that uuid is logged in.
|
* If there is a guest uuid in local storage, the guest with that uuid is logged in.
|
||||||
* If there is no guest uuid in local storage, a new guest is registered and logged in.
|
* If there is no guest uuid in local storage, a new guest is registered and logged in.
|
||||||
* The uuid is stored in local storage afterward.
|
* The uuid is stored in local storage afterward.
|
||||||
|
*
|
||||||
|
* @param ignoreSavedUuid If true, the function will not check for a saved guest uuid in local storage
|
||||||
*/
|
*/
|
||||||
export const loginOrRegisterGuest = async (): Promise<void> => {
|
export const loginOrRegisterGuest = async (ignoreSavedUuid?: boolean): Promise<void> => {
|
||||||
const userState = store.getState().user
|
const userState = store.getState().user
|
||||||
if (userState !== null) {
|
if (userState !== null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const guestUuid = window.localStorage.getItem('guestUuid')
|
const guestUuid = ignoreSavedUuid ? null : window.localStorage.getItem('guestUuid')
|
||||||
if (guestUuid === null) {
|
if (guestUuid === null) {
|
||||||
const { uuid } = await registerGuest()
|
const { uuid } = await registerGuest()
|
||||||
window.localStorage.setItem('guestUuid', uuid)
|
window.localStorage.setItem('guestUuid', uuid)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await logInGuest(guestUuid)
|
logInGuest(guestUuid)
|
||||||
await fetchAndSetUser()
|
.then(fetchAndSetUser)
|
||||||
|
.catch((error: unknown) => {
|
||||||
|
logger.error('Error logging in guest user', error)
|
||||||
|
return loginOrRegisterGuest(true)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue