wip: chore(backend): update and unify ESDoc and parameter names

Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
Erik Michelson 2025-05-20 13:16:02 +00:00
parent 327cc1f925
commit d68873de85
No known key found for this signature in database
GPG key ID: DB99ADDDC5C0AF82
32 changed files with 289 additions and 310 deletions

View file

@ -7,8 +7,6 @@ import { AliasDto } from '@hedgedoc/commons';
import { import {
Alias, Alias,
FieldNameAlias, FieldNameAlias,
FieldNameNote,
Note,
TableAlias, TableAlias,
TypeInsertAlias, TypeInsertAlias,
} from '@hedgedoc/database'; } from '@hedgedoc/database';
@ -64,8 +62,8 @@ export class AliasService {
* @throws {ForbiddenIdError} The requested alias is forbidden * @throws {ForbiddenIdError} The requested alias is forbidden
*/ */
async addAlias( async addAlias(
noteId: Note[FieldNameNote.id], noteId: number,
alias: Alias[FieldNameAlias.alias], alias: string,
transaction?: Knex, transaction?: Knex,
): Promise<void> { ): Promise<void> {
const dbActor: Knex = transaction ? transaction : this.knex; const dbActor: Knex = transaction ? transaction : this.knex;
@ -93,10 +91,7 @@ export class AliasService {
* @throws {NotInDBError} when the alias is not assigned to this note * @throws {NotInDBError} when the alias is not assigned to this note
* @throws {GenericDBError} when the database has an inconsistent state * @throws {GenericDBError} when the database has an inconsistent state
*/ */
async makeAliasPrimary( async makeAliasPrimary(noteId: number, alias: string): Promise<void> {
noteId: Note[FieldNameNote.id],
alias: Alias[FieldNameAlias.alias],
): Promise<void> {
await this.knex.transaction(async (transaction) => { await this.knex.transaction(async (transaction) => {
// First, set all existing aliases to not primary // First, set all existing aliases to not primary
const numberOfUpdatedEntries = await transaction(TableAlias) const numberOfUpdatedEntries = await transaction(TableAlias)
@ -106,7 +101,7 @@ export class AliasService {
.where(FieldNameAlias.noteId, noteId); .where(FieldNameAlias.noteId, noteId);
if (numberOfUpdatedEntries === 0) { if (numberOfUpdatedEntries === 0) {
throw new GenericDBError( throw new GenericDBError(
`The note does not exist or has no primary alias. This should never happen`, 'The note does not exist or has no primary alias. This should never happen',
this.logger.getContext(), this.logger.getContext(),
'makeAliasPrimary', 'makeAliasPrimary',
); );
@ -130,7 +125,7 @@ export class AliasService {
/** /**
* Removes the specified alias from the note * Removes the specified alias from the note
* This method only does not require the noteId since it can be obtained from the alias prior to deletion * This method only requires the alias since it can obtain the noteId from the alias prior to deletion
* *
* @param alias The alias to remove from the note * @param alias The alias to remove from the note
* @throws {ForbiddenIdError} The requested alias is forbidden * @throws {ForbiddenIdError} The requested alias is forbidden
@ -172,13 +167,14 @@ export class AliasService {
* Gets the primary alias of the note specified by the noteId * Gets the primary alias of the note specified by the noteId
* *
* @param noteId The id of the note to get the primary alias of * @param noteId The id of the note to get the primary alias of
* @param transaction The optional transaction to access the db
* @returns The primary alias of the note * @returns The primary alias of the note
* @throws {NotInDBError} The note has no primary alias which should mean that the note does not exist * @throws {NotInDBError} The note has no primary alias which should mean that the note does not exist
*/ */
async getPrimaryAliasByNoteId( async getPrimaryAliasByNoteId(
noteId: number, noteId: number,
transaction?: Knex, transaction?: Knex,
): Promise<Alias[FieldNameAlias.alias]> { ): Promise<string> {
const dbActor = transaction ?? this.knex; const dbActor = transaction ?? this.knex;
const primaryAlias = await dbActor(TableAlias) const primaryAlias = await dbActor(TableAlias)
.select(FieldNameAlias.alias) .select(FieldNameAlias.alias)
@ -187,7 +183,7 @@ export class AliasService {
.first(); .first();
if (primaryAlias === undefined) { if (primaryAlias === undefined) {
throw new NotInDBError( throw new NotInDBError(
`The noteId '${noteId}' has no primary alias.`, 'The note does not exist or has no primary alias. This should never happen',
this.logger.getContext(), this.logger.getContext(),
'getPrimaryAliasByNoteId', 'getPrimaryAliasByNoteId',
); );
@ -198,9 +194,10 @@ export class AliasService {
/** /**
* Gets all aliases of the note specified by the noteId * Gets all aliases of the note specified by the noteId
* *
* @param noteId The id of the note to get the primary alias of * @param noteId The id of the note to get the list of aliases for
* @returns The primary alias of the note * @param transaction The optional transaction to access the db
* @throws {NotInDBError} The note has no primary alias which should mean that the note does not exist * @returns The list of aliases for the note
* @throws {NotInDBError} The note with the specified id does not exist
*/ */
async getAllAliases( async getAllAliases(
noteId: number, noteId: number,
@ -212,7 +209,7 @@ export class AliasService {
.where(FieldNameAlias.noteId, noteId); .where(FieldNameAlias.noteId, noteId);
if (aliases.length === 0) { if (aliases.length === 0) {
throw new NotInDBError( throw new NotInDBError(
`The noteId '${noteId}' has no aliases. This should never happen.`, 'The note does not exist or has no aliases. This should never happen',
this.logger.getContext(), this.logger.getContext(),
'getAllAliases', 'getAllAliases',
); );
@ -230,7 +227,7 @@ export class AliasService {
* @throws {AlreadyInDBError} The requested alias already exists * @throws {AlreadyInDBError} The requested alias already exists
*/ */
async ensureAliasIsAvailable( async ensureAliasIsAvailable(
alias: Alias[FieldNameAlias.alias], alias: string,
transaction?: Knex, transaction?: Knex,
): Promise<void> { ): Promise<void> {
if (this.isAliasForbidden(alias)) { if (this.isAliasForbidden(alias)) {
@ -243,7 +240,7 @@ export class AliasService {
const isUsed = await this.isAliasUsed(alias, transaction); const isUsed = await this.isAliasUsed(alias, transaction);
if (isUsed) { if (isUsed) {
throw new AlreadyInDBError( throw new AlreadyInDBError(
`A note with the id or alias '${alias}' already exists.`, `A note with the alias '${alias}' already exists.`,
this.logger.getContext(), this.logger.getContext(),
'ensureAliasIsAvailable', 'ensureAliasIsAvailable',
); );
@ -254,17 +251,10 @@ export class AliasService {
* Checks if the provided alias is forbidden by configuration * Checks if the provided alias is forbidden by configuration
* *
* @param alias The alias to check * @param alias The alias to check
* @return {boolean} true if the alias is forbidden, false otherwise * @returns true if the alias is forbidden, false otherwise
*/ */
isAliasForbidden(alias: Alias[FieldNameAlias.alias]): boolean { isAliasForbidden(alias: string): boolean {
const forbidden = this.noteConfig.forbiddenNoteIds.includes(alias); return this.noteConfig.forbiddenNoteIds.includes(alias);
if (forbidden) {
this.logger.warn(
`A note with the alias '${alias}' is forbidden by the administrator.`,
'isAliasForbidden',
);
}
return forbidden;
} }
/** /**
@ -272,19 +262,16 @@ export class AliasService {
* *
* @param alias The alias to check * @param alias The alias to check
* @param transaction The optional transaction to access the db * @param transaction The optional transaction to access the db
* @return {boolean} true if the id or alias is already used, false otherwise * @returns true if the alias is already used, false otherwise
*/ */
async isAliasUsed( async isAliasUsed(alias: string, transaction?: Knex): Promise<boolean> {
alias: Alias[FieldNameAlias.alias],
transaction?: Knex,
): Promise<boolean> {
const dbActor = transaction ? transaction : this.knex; const dbActor = transaction ? transaction : this.knex;
const result = await dbActor(TableAlias) const result = await dbActor(TableAlias)
.select(FieldNameAlias.alias) .select(FieldNameAlias.alias)
.where(FieldNameAlias.alias, alias); .where(FieldNameAlias.alias, alias);
if (result.length === 1) { if (result.length === 1) {
this.logger.log( this.logger.log(
`A note with the id or alias '${alias}' already exists.`, `A note with the alias '${alias}' already exists.`,
'isAliasUsed', 'isAliasUsed',
); );
return true; return true;
@ -293,11 +280,11 @@ export class AliasService {
} }
/** /**
* Build the AliasDto from a note. * Returns alias information in the AliasDto format
*
* @param alias The alias to use * @param alias The alias to use
* @param isPrimaryAlias If the alias is the primary alias. * @param isPrimaryAlias Whether the alias is the primary alias.
* @throws {NotInDBError} The specified alias does not exist * @returns The built AliasDto
* @return {AliasDto} The built AliasDto
*/ */
toAliasDto(alias: string, isPrimaryAlias: boolean): AliasDto { toAliasDto(alias: string, isPrimaryAlias: boolean): AliasDto {
return { return {

View file

@ -4,12 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { ApiTokenDto, ApiTokenWithSecretDto } from '@hedgedoc/commons'; import { ApiTokenDto, ApiTokenWithSecretDto } from '@hedgedoc/commons';
import { import { ApiToken, FieldNameApiToken, TableApiToken } from '@hedgedoc/database';
ApiToken,
FieldNameApiToken,
TableApiToken,
TypeInsertApiToken,
} from '@hedgedoc/database';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Cron, Timeout } from '@nestjs/schedule'; import { Cron, Timeout } from '@nestjs/schedule';
import { randomBytes } from 'crypto'; import { randomBytes } from 'crypto';
@ -28,7 +23,8 @@ import {
hashApiToken, hashApiToken,
} from '../utils/password'; } from '../utils/password';
export const AUTH_TOKEN_PREFIX = 'hd2'; const AUTH_TOKEN_PREFIX = 'hd2';
const MESSAGE_TOKEN_INVALID = 'API token is invalid, expired or not found';
@Injectable() @Injectable()
export class ApiTokenService { export class ApiTokenService {
@ -46,21 +42,22 @@ export class ApiTokenService {
* The usage of this token is tracked in the database * The usage of this token is tracked in the database
* *
* @param tokenString The token string to validate and parse * @param tokenString The token string to validate and parse
* @return The userId associated with the token * @returns The userId associated with the token
* @throws TokenNotValidError if the token is not valid * @throws {TokenNotValidError} if the token is not valid
*/ */
async getUserIdForToken(tokenString: string): Promise<number> { async getUserIdForToken(tokenString: string): Promise<number> {
const [prefix, keyId, secret, ...rest] = tokenString.split('.'); const [prefix, keyId, secret, ...rest] = tokenString.split('.');
if (!keyId || !secret || prefix !== AUTH_TOKEN_PREFIX || rest.length > 0) { // We always expect 86 characters for the secret and 11 characters for the keyId
// as they are generated with 64 bytes and 8 bytes respectively and then converted to a base64url string
if (
keyId.length !== 11 ||
!secret ||
secret.length !== 86 ||
prefix !== AUTH_TOKEN_PREFIX ||
rest.length > 0
) {
throw new TokenNotValidError('Invalid API token format'); throw new TokenNotValidError('Invalid API token format');
} }
if (secret.length != 86) {
// We always expect 86 characters, as the secret is generated with 64 bytes
// and then converted to a base64url string
throw new TokenNotValidError(
`API token '${tokenString}' has incorrect length`,
);
}
return await this.knex.transaction(async (transaction) => { return await this.knex.transaction(async (transaction) => {
const token = await transaction(TableApiToken) const token = await transaction(TableApiToken)
.select( .select(
@ -71,7 +68,7 @@ export class ApiTokenService {
.where(FieldNameApiToken.id, keyId) .where(FieldNameApiToken.id, keyId)
.first(); .first();
if (token === undefined) { if (token === undefined) {
throw new TokenNotValidError('Token not found'); throw new TokenNotValidError(MESSAGE_TOKEN_INVALID);
} }
const tokenHash = token[FieldNameApiToken.secretHash]; const tokenHash = token[FieldNameApiToken.secretHash];
@ -88,16 +85,20 @@ export class ApiTokenService {
/** /**
* Creates a new API token for the given user * Creates a new API token for the given user
* We limit the number of tokens to 200 per user to avoid users losing track over their tokens.
* There is no technical limit to this.
*
* The returned secret is stored hashed in the database and therefore cannot be retrieved again.
* *
* @param userId The id of the user to create the token for * @param userId The id of the user to create the token for
* @param tokenLabel The label of the token * @param label The label of the token
* @param userDefinedValidUntil Maximum date until the token is valid, will be truncated to 2 years * @param userDefinedValidUntil Maximum date until the token is valid, will be truncated to 2 years
* @throws TooManyTokensError if the user already has 200 tokens
* @returns The created token together with the secret * @returns The created token together with the secret
* @throws {TooManyTokensError} if the user already has 200 tokens
*/ */
async createToken( async createToken(
userId: number, userId: number,
tokenLabel: string, label: string,
userDefinedValidUntil?: Date, userDefinedValidUntil?: Date,
): Promise<ApiTokenWithSecretDto> { ): Promise<ApiTokenWithSecretDto> {
return await this.knex.transaction(async (transaction) => { return await this.knex.transaction(async (transaction) => {
@ -105,16 +106,15 @@ export class ApiTokenService {
.select(FieldNameApiToken.id) .select(FieldNameApiToken.id)
.where(FieldNameApiToken.userId, userId); .where(FieldNameApiToken.userId, userId);
if (existingTokensForUser.length >= 200) { if (existingTokensForUser.length >= 200) {
// This is a very high ceiling unlikely to hinder legitimate usage,
// but should prevent possible attack vectors
throw new TooManyTokensError( throw new TooManyTokensError(
`User '${userId}' has already 200 API tokens and can't have more`, 'There is a maximum of 200 API tokens per user',
); );
} }
const secret = bufferToBase64Url(randomBytes(64)); const secret = bufferToBase64Url(randomBytes(64));
const keyId = bufferToBase64Url(randomBytes(8)); const keyId = bufferToBase64Url(randomBytes(8));
const accessTokenHash = hashApiToken(secret); const secretHash = hashApiToken(secret);
const fullToken = `${AUTH_TOKEN_PREFIX}.${keyId}.${secret}`;
// Tokens can only be valid for a maximum of 2 years // Tokens can only be valid for a maximum of 2 years
const maximumTokenValidity = new Date(); const maximumTokenValidity = new Date();
maximumTokenValidity.setTime( maximumTokenValidity.setTime(
@ -125,37 +125,34 @@ export class ApiTokenService {
const validUntil = isTokenLimitedToMaximumValidity const validUntil = isTokenLimitedToMaximumValidity
? maximumTokenValidity ? maximumTokenValidity
: userDefinedValidUntil; : userDefinedValidUntil;
const token: TypeInsertApiToken = { const createdAt = new Date();
await this.knex(TableApiToken).insert({
[FieldNameApiToken.id]: keyId, [FieldNameApiToken.id]: keyId,
[FieldNameApiToken.label]: tokenLabel, [FieldNameApiToken.label]: label,
[FieldNameApiToken.userId]: userId, [FieldNameApiToken.userId]: userId,
[FieldNameApiToken.secretHash]: accessTokenHash, [FieldNameApiToken.secretHash]: secretHash,
[FieldNameApiToken.validUntil]: validUntil, [FieldNameApiToken.validUntil]: validUntil,
[FieldNameApiToken.createdAt]: new Date(), [FieldNameApiToken.createdAt]: createdAt,
});
return {
label,
keyId,
createdAt: createdAt.toISOString(),
validUntil: validUntil.toISOString(),
lastUsedAt: null,
secret: fullToken,
}; };
await this.knex(TableApiToken).insert(token);
return this.toAuthTokenWithSecretDto(
{
...token,
[FieldNameApiToken.validUntil]:
token[FieldNameApiToken.validUntil].toISOString(),
[FieldNameApiToken.createdAt]:
token[FieldNameApiToken.createdAt].toISOString(),
[FieldNameApiToken.lastUsedAt]: null,
},
secret,
);
}); });
} }
/** /**
* Ensures that the given token secret is valid for the given token * Ensures that a token is valid by evaluating the expiry date as well as comparing secret and stored hash
* This method does not return any value but throws an error if the token is not valid * This method does not return any value but throws an error if the token is not valid
* *
* @param secret The secret to compare against the hash from the database * @param secret The secret to compare against the hash from the database
* @param tokenHash The hash from the database * @param tokenHash The hash from the database
* @param validUntil Expiry of the API token * @param validUntil Expiry of the API token
* @throws TokenNotValidError if the token is invalid * @throws {TokenNotValidError} if the token is invalid
*/ */
ensureTokenIsValid( ensureTokenIsValid(
secret: string, secret: string,
@ -164,14 +161,12 @@ export class ApiTokenService {
): void { ): void {
// First, verify token expiry is not in the past (cheap operation) // First, verify token expiry is not in the past (cheap operation)
if (validUntil.getTime() < new Date().getTime()) { if (validUntil.getTime() < new Date().getTime()) {
throw new TokenNotValidError( throw new TokenNotValidError(MESSAGE_TOKEN_INVALID);
`Auth token is not valid since ${validUntil.toISOString()}`,
);
} }
// Second, verify the secret (costly operation) // Second, verify the secret (costly operation)
if (!checkTokenEquality(secret, tokenHash)) { if (!checkTokenEquality(secret, tokenHash)) {
throw new TokenNotValidError(`Secret does not match token hash`); throw new TokenNotValidError(MESSAGE_TOKEN_INVALID);
} }
} }
@ -179,7 +174,7 @@ export class ApiTokenService {
* Returns all tokens of a user * Returns all tokens of a user
* *
* @param userId The id of the user to get the tokens for * @param userId The id of the user to get the tokens for
* @return The tokens of the user * @returns A list of the user's tokens as ApiToken objects
*/ */
getTokensOfUserById(userId: number): Promise<ApiToken[]> { getTokensOfUserById(userId: number): Promise<ApiToken[]> {
return this.knex(TableApiToken) return this.knex(TableApiToken)
@ -205,10 +200,10 @@ export class ApiTokenService {
} }
/** /**
* Converts an ApiToken to an ApiTokenDto * Formats an ApiToken object from the database to an ApiTokenDto
* *
* @param apiToken The token to convert * @param apiToken The token object to convert
* @return The converted token * @returns The built ApiTokenDto
*/ */
toAuthTokenDto(apiToken: ApiToken): ApiTokenDto { toAuthTokenDto(apiToken: ApiToken): ApiTokenDto {
return { return {
@ -224,25 +219,6 @@ export class ApiTokenService {
}; };
} }
/**
* Converts an ApiToken to an ApiTokenWithSecretDto
*
* @param apiToken The token to convert
* @param secret The secret of the token
* @return The converted token
*/
toAuthTokenWithSecretDto(
apiToken: ApiToken,
secret: string,
): ApiTokenWithSecretDto {
const tokenDto = this.toAuthTokenDto(apiToken);
const fullToken = `${AUTH_TOKEN_PREFIX}.${tokenDto.keyId}.${secret}`;
return {
...tokenDto,
secret: fullToken,
};
}
// Deletes all invalid tokens every sunday on 3:00 AM // Deletes all invalid tokens every sunday on 3:00 AM
@Cron('0 0 3 * * 0') @Cron('0 0 3 * * 0')
async handleCron(): Promise<void> { async handleCron(): Promise<void> {
@ -264,7 +240,7 @@ export class ApiTokenService {
.where(FieldNameApiToken.validUntil, '<', new Date()) .where(FieldNameApiToken.validUntil, '<', new Date())
.delete(); .delete();
this.logger.log( this.logger.log(
`${numberOfDeletedTokens} invalid AuthTokens were purged from the DB.`, `${numberOfDeletedTokens} expired API tokens were purged from the DB`,
'removeInvalidTokens', 'removeInvalidTokens',
); );
} }

View file

@ -51,7 +51,7 @@ export class LocalController {
@Body() registerDto: RegisterDto, @Body() registerDto: RegisterDto,
): Promise<void> { ): Promise<void> {
await this.localIdentityService.checkPasswordStrength(registerDto.password); await this.localIdentityService.checkPasswordStrength(registerDto.password);
const userId = await this.localIdentityService.createLocalIdentity( const userId = await this.localIdentityService.createUserWithLocalIdentity(
registerDto.username, registerDto.username,
registerDto.password, registerDto.password,
registerDto.displayName, registerDto.displayName,

View file

@ -8,14 +8,7 @@ import {
PendingUserConfirmationDto, PendingUserConfirmationDto,
PendingUserInfoDto, PendingUserInfoDto,
} from '@hedgedoc/commons'; } from '@hedgedoc/commons';
import { import { FieldNameIdentity, Identity, TableIdentity } from '@hedgedoc/database';
FieldNameIdentity,
FieldNameUser,
Identity,
TableIdentity,
TypeInsertIdentity,
User,
} from '@hedgedoc/database';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex'; import { Knex } from 'knex';
import { InjectConnection } from 'nest-knexjs'; import { InjectConnection } from 'nest-knexjs';
@ -44,7 +37,7 @@ export class IdentityService {
* Determines if the identity should be updated * Determines if the identity should be updated
* *
* @param authProviderIdentifier The identifier of the auth source * @param authProviderIdentifier The identifier of the auth source
* @return true if the authProviderIdentifier is the sync source, false otherwise * @returns true if the authProviderIdentifier is the sync source, false otherwise
*/ */
mayUpdateIdentity(authProviderIdentifier: string): boolean { mayUpdateIdentity(authProviderIdentifier: string): boolean {
return this.authConfig.common.syncSource === authProviderIdentifier; return this.authConfig.common.syncSource === authProviderIdentifier;
@ -53,10 +46,11 @@ export class IdentityService {
/** /**
* Retrieve an identity from the information received from an auth provider. * Retrieve an identity from the information received from an auth provider.
* *
* @param authProviderUserId - the userId of the wanted identity * @param authProviderUserId the userId of the wanted identity
* @param authProviderType - the providerType of the wanted identity * @param authProviderType the providerType of the wanted identity
* @param authProviderIdentifier - optional name of the provider if multiple exist * @param authProviderIdentifier optional name of the provider if multiple exist
* @return * @returns The found identity
* @throws {NotInDBError} if the identity is not found
*/ */
async getIdentityFromUserIdAndProviderType( async getIdentityFromUserIdAndProviderType(
authProviderUserId: string, authProviderUserId: string,
@ -80,13 +74,12 @@ export class IdentityService {
/** /**
* Creates a new generic identity. * Creates a new generic identity.
* *
* @param userId - the user the identity should be added to * @param userId the user the identity should be added to
* @param authProviderType - the providerType of the identity * @param authProviderType the providerType of the identity
* @param authProviderIdentifier - the providerIdentifier of the identity * @param authProviderIdentifier the providerIdentifier of the identity
* @param authProviderUserId - the userId the identity should have * @param authProviderUserId the userId the identity should have
* @param passwordHash - the password hash if the identiy uses that. * @param passwordHash the password hash if the identiy uses that.
* @param transaction - the database transaction to use if any * @param transaction the database transaction to use if any
* @return the new local identity
*/ */
async createIdentity( async createIdentity(
userId: number, userId: number,
@ -97,14 +90,13 @@ export class IdentityService {
transaction?: Knex, transaction?: Knex,
): Promise<void> { ): Promise<void> {
const dbActor = transaction ?? this.knex; const dbActor = transaction ?? this.knex;
const identity: TypeInsertIdentity = { await dbActor(TableIdentity).insert({
[FieldNameIdentity.userId]: userId, [FieldNameIdentity.userId]: userId,
[FieldNameIdentity.providerType]: authProviderType, [FieldNameIdentity.providerType]: authProviderType,
[FieldNameIdentity.providerIdentifier]: authProviderIdentifier, [FieldNameIdentity.providerIdentifier]: authProviderIdentifier,
[FieldNameIdentity.providerUserId]: authProviderUserId, [FieldNameIdentity.providerUserId]: authProviderUserId,
[FieldNameIdentity.passwordHash]: passwordHash ?? null, [FieldNameIdentity.passwordHash]: passwordHash ?? null,
}; });
await dbActor(TableIdentity).insert(identity);
} }
/** /**
@ -118,7 +110,7 @@ export class IdentityService {
* @param email The email address of the new user * @param email The email address of the new user
* @param photoUrl The URL to the new user's profile picture * @param photoUrl The URL to the new user's profile picture
* @param passwordHash The optional password hash, only required for local identities * @param passwordHash The optional password hash, only required for local identities
* @return The id of the newly created user * @returns The id of the newly created user
*/ */
async createUserWithIdentity( async createUserWithIdentity(
authProviderType: AuthProviderType, authProviderType: AuthProviderType,
@ -129,7 +121,7 @@ export class IdentityService {
email: string | null, email: string | null,
photoUrl: string | null, photoUrl: string | null,
passwordHash?: string, passwordHash?: string,
): Promise<User[FieldNameUser.id]> { ): Promise<number> {
return await this.knex.transaction(async (transaction) => { return await this.knex.transaction(async (transaction) => {
const userId = await this.usersService.createUser( const userId = await this.usersService.createUser(
username, username,
@ -151,14 +143,14 @@ export class IdentityService {
} }
/** /**
* Create a user with identity from pending user confirmation data. * Create a user with identity from pending user confirmation data
* *
* @param sessionUserData The data we got from the authProvider itself * @param sessionUserData The data we got from the authProvider itself
* @param pendingUserConfirmationData The data the user entered while confirming their account * @param pendingUserConfirmationData The data the user entered while confirming their account
* @param authProviderType The type of the auth provider * @param authProviderType The type of the auth provider
* @param authProviderIdentifier The identifier of the auth provider * @param authProviderIdentifier The identifier of the auth provider
* @param authProviderUserId The id of the user in the auth system * @param authProviderUserId The id of the user in the auth system
* @return The id of the newly created user * @returns The id of the newly created user
*/ */
async createUserWithIdentityFromPendingUserConfirmation( async createUserWithIdentityFromPendingUserConfirmation(
sessionUserData: PendingUserInfoDto, sessionUserData: PendingUserInfoDto,
@ -166,7 +158,7 @@ export class IdentityService {
authProviderType: AuthProviderType, authProviderType: AuthProviderType,
authProviderIdentifier: string, authProviderIdentifier: string,
authProviderUserId: string, authProviderUserId: string,
): Promise<User[FieldNameUser.id]> { ): Promise<number> {
const profileEditsAllowed = this.authConfig.common.allowProfileEdits; const profileEditsAllowed = this.authConfig.common.allowProfileEdits;
const chooseUsernameAllowed = this.authConfig.common.allowChooseUsername; const chooseUsernameAllowed = this.authConfig.common.allowChooseUsername;

View file

@ -32,7 +32,7 @@ const LDAP_ERROR_MAP: Record<string, string> = {
'775': 'User account locked', '775': 'User account locked',
default: 'Invalid username/password', default: 'Invalid username/password',
/* eslint-enable @typescript-eslint/naming-convention */ /* eslint-enable @typescript-eslint/naming-convention */
}; } as const;
@Injectable() @Injectable()
export class LdapService { export class LdapService {
@ -45,15 +45,14 @@ export class LdapService {
} }
/** /**
* Try to log in the user with the given credentials. * Tries to log in the user with the given credentials
* *
* @param ldapConfig {LdapConfig} - the ldap config to use * @param ldapConfig The ldap config to use
* @param username {string} - the username to log in with * @param username The user-provided username
* @param password {string} - the password to log in with * @param password The user-provided password
* @returns The user info of the user that logged in * @returns The user info of the user that logged in
* @throws {UnauthorizedException} - the user has given us incorrect credentials * @throws {UnauthorizedException} if the user has given us incorrect credentials
* @throws {InternalServerErrorException} - if there are errors that we can't assign to wrong credentials * @throws {InternalServerErrorException} if there are errors that we can't assign to wrong credentials
* @private
*/ */
getUserInfoFromLdap( getUserInfoFromLdap(
ldapConfig: LdapConfig, ldapConfig: LdapConfig,
@ -119,11 +118,11 @@ export class LdapService {
} }
/** /**
* Get and return the correct ldap config from the list of available configs. * Fetches the correct LDAP config from the list of available configs
* @param {string} ldapIdentifier the identifier for the ldap config to be used *
* @returns {LdapConfig} - the ldap config with the given identifier * @param ldapIdentifier The identifier for the LDAP config to be used
* @throws {NotFoundException} - there is no ldap config with the given identifier * @returns The LDAP config with the given identifier
* @private * @throws {NotFoundException} if there is no LDAP config with the given identifier
*/ */
getLdapConfig(ldapIdentifier: string): LdapConfig { getLdapConfig(ldapIdentifier: string): LdapConfig {
const ldapConfig = this.authConfig.ldap.find( const ldapConfig = this.authConfig.ldap.find(
@ -131,18 +130,21 @@ export class LdapService {
); );
if (!ldapConfig) { if (!ldapConfig) {
this.logger.warn( this.logger.warn(
`The LDAP Config '${ldapIdentifier}' was requested, but doesn't exist`, `The LDAP config '${ldapIdentifier}' was requested, but doesn't exist`,
);
throw new NotFoundException(
`There is no LDAP config '${ldapIdentifier}'`,
); );
throw new NotFoundException(`There is no ldapConfig '${ldapIdentifier}'`);
} }
return ldapConfig; return ldapConfig;
} }
/** /**
* This method transforms the ldap error codes we receive into correct errors. * This method transforms the LDAP error codes we receive into correct errors.
*
* It's very much inspired by https://github.com/vesse/passport-ldapauth/blob/b58c60000a7cc62165b112274b80c654adf59fff/lib/passport-ldapauth/strategy.js#L261 * It's very much inspired by https://github.com/vesse/passport-ldapauth/blob/b58c60000a7cc62165b112274b80c654adf59fff/lib/passport-ldapauth/strategy.js#L261
* @returns {HttpException} - the matching HTTP exception to throw to the client * @returns {HttpException} The matching HTTP exception to throw to the client
* @throws {UnauthorizedException} if error indicates that the user is not allowed to log in * @throws {UnauthorizedException} if the error indicates that the user is not allowed to log in
* @throws {InternalServerErrorException} in every other case * @throws {InternalServerErrorException} in every other case
*/ */
private getLdapException( private getLdapException(

View file

@ -4,13 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { AuthProviderType } from '@hedgedoc/commons'; import { AuthProviderType } from '@hedgedoc/commons';
import { import { FieldNameIdentity, Identity, TableIdentity } from '@hedgedoc/database';
FieldNameIdentity,
FieldNameUser,
Identity,
TableIdentity,
User,
} from '@hedgedoc/database';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { import {
OptionsGraph, OptionsGraph,
@ -63,18 +57,18 @@ export class LocalService {
} }
/** /**
* Create a new identity for internal auth * Creates a new user with an identity for internal auth
* *
* @param username The username of the new identity * @param username The username of the new identity
* @param password The password the identity should have * @param password The password the identity should have
* @param displayName The display name of the new identity * @param displayName The display name of the new identity
* @returns {Identity} the new local identity * @returns The id of the newly created user
*/ */
async createLocalIdentity( async createUserWithLocalIdentity(
username: string, username: string,
password: string, password: string,
displayName: string, displayName: string,
): Promise<User[FieldNameUser.id]> { ): Promise<number> {
const passwordHash = await hashPassword(password); const passwordHash = await hashPassword(password);
return await this.identityService.createUserWithIdentity( return await this.identityService.createUserWithIdentity(
AuthProviderType.LOCAL, AuthProviderType.LOCAL,
@ -89,12 +83,12 @@ export class LocalService {
} }
/** /**
* @async * Updates the password hash for the local identity of the specified the user
* Update the internal password of the specified the user *
* @param {User} userId - the user, which identity should be updated * @param userId The user, whose local identity should be updated
* @param {string} newPassword - the new password * @param newPassword The new password
* @throws {NoLocalIdentityError} the specified user has no internal identity * @throws {NoLocalIdentityError} if the specified user has no local identity
* @return {Identity} the changed identity * @throws {PasswordTooWeakError} if the password is too weak
*/ */
async updateLocalPassword( async updateLocalPassword(
userId: number, userId: number,
@ -112,12 +106,11 @@ export class LocalService {
} }
/** /**
* @async * Checks if the user and password combination matches for the local identity and returns the local identity on success
* Checks if the user and password combination matches *
* @param {string} username - the user to use * @param username The user to use
* @param {string} password - the password to use * @param password The password to use
* @throws {InvalidCredentialsError} the password and user do not match * @throws {InvalidCredentialsError} if the credentials are invalid
* @throws {NoLocalIdentityError} the specified user has no internal identity
*/ */
async checkLocalPassword( async checkLocalPassword(
username: string, username: string,
@ -129,12 +122,11 @@ export class LocalService {
AuthProviderType.LOCAL, AuthProviderType.LOCAL,
null, null,
); );
if ( const passwordValid = await checkPassword(
!(await checkPassword( password,
password, identity[FieldNameIdentity.passwordHash] ?? '',
identity[FieldNameIdentity.passwordHash] ?? '', );
)) if (!passwordValid) {
) {
throw new InvalidCredentialsError( throw new InvalidCredentialsError(
'Username or password is not correct', 'Username or password is not correct',
this.logger.getContext(), this.logger.getContext(),
@ -145,11 +137,12 @@ export class LocalService {
} }
/** /**
* @async * Checks if the password is strong and long enough
* Check if the password is strong and long enough. *
* This check is performed against the minimalPasswordStrength of the {@link AuthConfig}. * This check is performed against the minimalPasswordStrength of the {@link AuthConfig}.
* @param {string} password - the password to check *
* @throws {PasswordTooWeakError} the password is too weak * @param password The password to check
* @throws {PasswordTooWeakError} if the password is too weak
*/ */
async checkPasswordStrength(password: string): Promise<void> { async checkPasswordStrength(password: string): Promise<void> {
if (password.length < 6) { if (password.length < 6) {

View file

@ -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.
* *
@ -231,20 +232,18 @@ export class OidcService {
}; };
request.session.providerUserId = userId; request.session.providerUserId = userId;
request.session.newUserData = newUserData; request.session.newUserData = newUserData;
// Cleanup: The code isn't necessary anymore // Cleanup: The OIDC login code and state are not required anymore
request.session.oidcLoginCode = undefined; request.session.oidcLoginCode = undefined;
request.session.oidcLoginState = undefined; request.session.oidcLoginState = undefined;
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,
@ -263,6 +262,7 @@ export class OidcService {
oidcIdentifier, oidcIdentifier,
); );
} catch (e) { } catch (e) {
// Catch not-found errors when registration via OIDC is enabled and return null instead
if (e instanceof NotInDBError) { if (e instanceof NotInDBError) {
if (!clientConfig.config.enableRegistration) { if (!clientConfig.config.enableRegistration) {
throw new ForbiddenException( throw new ForbiddenException(
@ -277,11 +277,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;
@ -304,12 +303,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,

View file

@ -16,10 +16,10 @@ import { ConsoleLoggerService } from '../logger/console-logger.service';
/** /**
* This guard checks if a session is present. * This guard checks if a session is present.
* *
* If there is a username in `request.session.username` it will try to get this user from the database and put it into `request.user`. See {@link RequestUser}. * It checks if the session contains a `userId` and an `authProviderType`. If both are present, they are added to the request object.
* If there is no `request.session.username`, but any PermissionLevel is configured, `request.session.authProvider` is set to `guest` to indicate a guest user. * Otherwise, an `UnauthorizedException` is thrown.
* *
* @throws UnauthorizedException * @throws UnauthorizedException if the session is not present or does not contain a `userId` or `authProviderType`.
*/ */
@Injectable() @Injectable()
export class SessionGuard implements CanActivate { export class SessionGuard implements CanActivate {
@ -33,7 +33,7 @@ export class SessionGuard implements CanActivate {
const authProviderType = request.session?.authProviderType; const authProviderType = request.session?.authProviderType;
if (!userId || !authProviderType) { if (!userId || !authProviderType) {
this.logger.debug('The user has no session.'); this.logger.debug('The user has no session.');
throw new UnauthorizedException("You're not logged in"); throw new UnauthorizedException('You have no active session');
} }
request.userId = userId; request.userId = userId;
request.authProviderType = authProviderType; request.authProviderType = authProviderType;

View file

@ -18,6 +18,12 @@ import {
extractDescriptionFromZodIssue, extractDescriptionFromZodIssue,
} from './zod-error-message'; } from './zod-error-message';
/**
* Validates that a given URL is valid, uses the HTTP or HTTPS protocol, and does not end with a slash.
*
* @param value The URL string to validate.
* @param ctx The Zod refinement context.
*/
function validateUrl(value: string | undefined, ctx: RefinementCtx): void { function validateUrl(value: string | undefined, ctx: RefinementCtx): void {
if (!value) { if (!value) {
return z.NEVER; return z.NEVER;

View file

@ -0,0 +1,6 @@
# Migrations
The migrations are loaded and executed by Knex itself. It seems Knex expects CommonJS modules and does not support
TypeScript. Additionally, there were problems when importing types from TypeScript from the backend, or from the
commons package due to other (ESM-only) dependencies. Therefore, the migrations and database types use their own
CommonJS package.

View file

@ -24,7 +24,7 @@ export async function seed(knex: Knex): Promise<void> {
{ {
[FieldNameUser.username]: null, [FieldNameUser.username]: null,
[FieldNameUser.guestUuid]: '55b4618a-d5f3-4320-93d3-f3501c73d72b', [FieldNameUser.guestUuid]: '55b4618a-d5f3-4320-93d3-f3501c73d72b',
[FieldNameUser.displayName]: 'Gast 1', [FieldNameUser.displayName]: 'Guest 1',
[FieldNameUser.photoUrl]: null, [FieldNameUser.photoUrl]: null,
[FieldNameUser.email]: null, [FieldNameUser.email]: null,
[FieldNameUser.authorStyle]: 1, [FieldNameUser.authorStyle]: 1,

View file

@ -40,6 +40,11 @@ export class FrontendConfigService {
this.logger.setContext(FrontendConfigService.name); this.logger.setContext(FrontendConfigService.name);
} }
/**
* Returns the config options for the frontend
*
* @returns A frontend config DTO
*/
async getFrontendConfig(): Promise<FrontendConfigDto> { async getFrontendConfig(): Promise<FrontendConfigDto> {
return { return {
guestAccess: this.noteConfig.guestAccess, guestAccess: this.noteConfig.guestAccess,
@ -58,6 +63,11 @@ export class FrontendConfigService {
}; };
} }
/**
* Reads the auth providers from the config and returns them
*
* @returns An array of auth provider DTOs
*/
private getAuthProviders(): AuthProviderDto[] { private getAuthProviders(): AuthProviderDto[] {
const providers: AuthProviderDto[] = []; const providers: AuthProviderDto[] = [];
if (this.authConfig.local.enableLogin) { if (this.authConfig.local.enableLogin) {
@ -84,6 +94,11 @@ export class FrontendConfigService {
return providers; return providers;
} }
/**
* Reads the branding from the config and returns it
*
* @returns A branding DTO
*/
private getBranding(): BrandingDto { private getBranding(): BrandingDto {
return { return {
logo: this.customizationConfig.branding.customLogo logo: this.customizationConfig.branding.customLogo
@ -93,6 +108,11 @@ export class FrontendConfigService {
}; };
} }
/**
* Reads the special URLs like imprint or privacy policy from the config and returns them
*
* @returns A special URL DTO
*/
private getSpecialUrls(): SpecialUrlDto { private getSpecialUrls(): SpecialUrlDto {
return { return {
imprint: this.customizationConfig.specialUrls.imprint imprint: this.customizationConfig.specialUrls.imprint

View file

@ -4,11 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { GroupInfoDto } from '@hedgedoc/commons'; import { GroupInfoDto } from '@hedgedoc/commons';
import { import { FieldNameGroup, TableGroup } from '@hedgedoc/database';
FieldNameGroup,
TableGroup,
TypeInsertGroup,
} from '@hedgedoc/database';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Knex } from 'knex'; import { Knex } from 'knex';
import { InjectConnection } from 'nest-knexjs'; import { InjectConnection } from 'nest-knexjs';
@ -32,16 +28,15 @@ export class GroupsService {
* *
* @param name The group name as identifier the new group shall have * @param name The group name as identifier the new group shall have
* @param displayName The display name the new group shall have * @param displayName The display name the new group shall have
* @throws {AlreadyInDBError} The group name is already taken * @throws {AlreadyInDBError} if the group name is already taken
*/ */
async createGroup(name: string, displayName: string): Promise<void> { async createGroup(name: string, displayName: string): Promise<void> {
const group: TypeInsertGroup = {
[FieldNameGroup.name]: name,
[FieldNameGroup.displayName]: displayName,
[FieldNameGroup.isSpecial]: false,
};
try { try {
await this.knex(TableGroup).insert(group); await this.knex(TableGroup).insert({
[FieldNameGroup.name]: name,
[FieldNameGroup.displayName]: displayName,
[FieldNameGroup.isSpecial]: false,
});
} catch { } catch {
const message = `A group with the name '${name}' already exists.`; const message = `A group with the name '${name}' already exists.`;
this.logger.debug(message, 'createGroup'); this.logger.debug(message, 'createGroup');
@ -53,7 +48,7 @@ export class GroupsService {
* Fetches a group by its identifier name * Fetches a group by its identifier name
* *
* @param name Name of the group to query * @param name Name of the group to query
* @return The group * @returns The group's metadata
* @throws {NotInDBError} if there is no group with this name * @throws {NotInDBError} if there is no group with this name
*/ */
async getGroupInfoDtoByName(name: string): Promise<GroupInfoDto> { async getGroupInfoDtoByName(name: string): Promise<GroupInfoDto> {
@ -76,7 +71,7 @@ export class GroupsService {
* *
* @param name Name of the group to query * @param name Name of the group to query
* @param transaction The optional database transaction to use * @param transaction The optional database transaction to use
* @return The groupId * @returns The groupId
* @throws {NotInDBError} if there is no group with this name * @throws {NotInDBError} if there is no group with this name
*/ */
async getGroupIdByName(name: string, transaction?: Knex): Promise<number> { async getGroupIdByName(name: string, transaction?: Knex): Promise<number> {

View file

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file) * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
* *
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -12,7 +12,7 @@ export interface MediaBackend {
* @param buffer File data * @param buffer File data
* @param fileType File type result * @param fileType File type result
* @throws {MediaBackendError} - there was an error saving the file * @throws {MediaBackendError} - there was an error saving the file
* @return The internal backend data, which should be saved * @returns The internal backend data, which should be saved
*/ */
saveFile( saveFile(
uuid: string, uuid: string,
@ -33,7 +33,7 @@ export interface MediaBackend {
* @param uuid Unique identifier of the uploaded file * @param uuid Unique identifier of the uploaded file
* @param backendData Internal backend data * @param backendData Internal backend data
* @throws {MediaBackendError} - there was an error getting the file * @throws {MediaBackendError} - there was an error getting the file
* @return Public accessible URL of the file * @returns Public accessible URL of the file
*/ */
getFileUrl(uuid: string, backendData: string | null): Promise<string>; getFileUrl(uuid: string, backendData: string | null): Promise<string>;
} }

View file

@ -81,7 +81,7 @@ export class MediaService {
* @param fileBuffer The buffer with the file contents to save * @param fileBuffer The buffer with the file contents to save
* @param userId Id of the user who uploaded this file * @param userId Id of the user who uploaded this file
* @param noteId Id of the note which will be associated with the new file * @param noteId Id of the note which will be associated with the new file
* @return The created MediaUpload entity * @returns The created MediaUpload entity
* @throws {ClientError} if the MIME type of the file is not supported * @throws {ClientError} if the MIME type of the file is not supported
* @throws {NotInDBError} if the note or user is not in the database * @throws {NotInDBError} if the note or user is not in the database
* @throws {MediaBackendError} if there was an error saving the file * @throws {MediaBackendError} if there was an error saving the file
@ -123,7 +123,7 @@ export class MediaService {
} }
/** /**
* @async *
* Try to delete the specified file. * Try to delete the specified file.
* @param {uuid} uuid - the name of the file to delete. * @param {uuid} uuid - the name of the file to delete.
* @throws {MediaBackendError} - there was an error deleting the file * @throws {MediaBackendError} - there was an error deleting the file
@ -150,10 +150,10 @@ export class MediaService {
} }
/** /**
* @async *
* Get the URL of the file. * Get the URL of the file.
* @param {string} uuid - the uuid of the file to get the URL for. * @param uuid - the uuid of the file to get the URL for.
* @return {string} the URL of the file. * @returns the URL of the file.
* @throws {MediaBackendError} - there was an error retrieving the url * @throws {MediaBackendError} - there was an error retrieving the url
*/ */
async getFileUrl(uuid: string): Promise<string> { async getFileUrl(uuid: string): Promise<string> {
@ -178,9 +178,9 @@ export class MediaService {
} }
/** /**
* @async *
* Find a file entry by its UUID. * Find a file entry by its UUID.
* @param {string} uuid - The UUID of the MediaUpload entity to find. * @param uuid - The UUID of the MediaUpload entity to find.
* @returns {MediaUpload} - the MediaUpload entity if found. * @returns {MediaUpload} - the MediaUpload entity if found.
* @throws {NotInDBError} - the MediaUpload entity with the provided UUID is not found in the database. * @throws {NotInDBError} - the MediaUpload entity with the provided UUID is not found in the database.
*/ */
@ -196,10 +196,10 @@ export class MediaService {
} }
/** /**
* @async *
* List all uploads by a specific user * List all uploads by a specific user
* @param {number} userId - the specific user * @param {number} userId - the specific user
* @return {MediaUpload[]} arary of media uploads owned by the user * @returns {MediaUpload[]} arary of media uploads owned by the user
*/ */
async getMediaUploadUuidsByUserId( async getMediaUploadUuidsByUserId(
userId: number, userId: number,
@ -211,10 +211,10 @@ export class MediaService {
} }
/** /**
* @async *
* List all uploads to a specific note * List all uploads to a specific note
* @param {number} noteId - the specific user * @param {number} noteId - the specific user
* @return {MediaUpload[]} array of media uploads owned by the user * @returns {MediaUpload[]} array of media uploads owned by the user
*/ */
async getMediaUploadUuidsByNoteId( async getMediaUploadUuidsByNoteId(
noteId: number, noteId: number,
@ -228,9 +228,9 @@ export class MediaService {
} }
/** /**
* @async *
* Set the note of a mediaUpload to null * Set the note of a mediaUpload to null
* @param {string} uuid - the media upload to be changed * @param uuid - the media upload to be changed
*/ */
async removeNoteFromMediaUpload(uuid: string): Promise<void> { async removeNoteFromMediaUpload(uuid: string): Promise<void> {
this.logger.debug( this.logger.debug(

View file

@ -79,7 +79,7 @@ export class NoteService {
* Get all notes owned by a user * Get all notes owned by a user
* *
* @param userId The id of the user who owns the notes * @param userId The id of the user who owns the notes
* @return Array of notes owned by the user * @returns Array of notes owned by the user
*/ */
async getUserNoteIds(userId: number): Promise<number[]> { async getUserNoteIds(userId: number): Promise<number[]> {
const result = await this.knex(TableNote) const result = await this.knex(TableNote)
@ -94,7 +94,7 @@ export class NoteService {
* @param noteContent The content of the new note, in most cases an empty string * @param noteContent The content of the new note, in most cases an empty string
* @param givenAlias An optional alias the note should have * @param givenAlias An optional alias the note should have
* @param ownerUserId The owner of the note * @param ownerUserId The owner of the note
* @return The newly created note * @returns The newly created note
* @throws {AlreadyInDBError} a note with the requested id or aliases already exists * @throws {AlreadyInDBError} a note with the requested id or aliases already exists
* @throws {ForbiddenIdError} the requested id or aliases is forbidden * @throws {ForbiddenIdError} the requested id or aliases is forbidden
* @throws {MaximumDocumentLengthExceededError} the noteContent is longer than the maxDocumentLength * @throws {MaximumDocumentLengthExceededError} the noteContent is longer than the maxDocumentLength
@ -197,7 +197,7 @@ export class NoteService {
* @param noteId the note to use * @param noteId the note to use
* @param transaction The optional database transaction to use * @param transaction The optional database transaction to use
* @throws {NotInDBError} the note is not in the DB * @throws {NotInDBError} the note is not in the DB
* @return {string} the content of the note * @returns the content of the note
*/ */
async getNoteContent(noteId: number, transaction?: Knex): Promise<string> { async getNoteContent(noteId: number, transaction?: Knex): Promise<string> {
const realtimeContent = this.realtimeNoteStore const realtimeContent = this.realtimeNoteStore
@ -220,7 +220,7 @@ export class NoteService {
* *
* @param alias the notes id or aliases * @param alias the notes id or aliases
* @param transaction The optional database transaction to use * @param transaction The optional database transaction to use
* @return the note id * @returns the note id
* @throws {NotInDBError} there is no note with this id or aliases * @throws {NotInDBError} there is no note with this id or aliases
* @throws {ForbiddenIdError} the requested id or aliases is forbidden * @throws {ForbiddenIdError} the requested id or aliases is forbidden
*/ */
@ -280,7 +280,7 @@ export class NoteService {
* *
* @param noteId - the note * @param noteId - the note
* @param noteContent - the new content * @param noteContent - the new content
* @return the note with a new revision and new content * @returns the note with a new revision and new content
* @throws {NotInDBError} there is no note with this id or aliases * @throws {NotInDBError} there is no note with this id or aliases
*/ */
async updateNote(noteId: number, noteContent: string): Promise<void> { async updateNote(noteId: number, noteContent: string): Promise<void> {
@ -292,7 +292,7 @@ export class NoteService {
* Build NotePermissionsDto from a note. * Build NotePermissionsDto from a note.
* @param noteId The id of the ntoe to get the permissions for * @param noteId The id of the ntoe to get the permissions for
* @param transaction The optional database transaction to use * @param transaction The optional database transaction to use
* @return The built NotePermissionDto * @returns The built NotePermissionDto
*/ */
async toNotePermissionsDto( async toNotePermissionsDto(
noteId: number, noteId: number,
@ -374,11 +374,11 @@ export class NoteService {
} }
/** /**
* @async *
* Build NoteMetadataDto from a note. * Build NoteMetadataDto from a note.
* @param noteId The if of the note to get the metadata for * @param noteId The if of the note to get the metadata for
* @param transaction The optional database transaction to use * @param transaction The optional database transaction to use
* @return The built NoteMetadataDto * @returns The built NoteMetadataDto
*/ */
async toNoteMetadataDto( async toNoteMetadataDto(
noteId: number, noteId: number,
@ -473,7 +473,7 @@ export class NoteService {
* Gets the note data for the note DTO * Gets the note data for the note DTO
* *
* @param noteId The id of the note to transform * @param noteId The id of the note to transform
* @return {NoteDto} the built NoteDto * @returns {NoteDto} the built NoteDto
*/ */
async toNoteDto(noteId: number): Promise<NoteDto> { async toNoteDto(noteId: number): Promise<NoteDto> {
return await this.knex.transaction(async (transaction) => { return await this.knex.transaction(async (transaction) => {

View file

@ -18,7 +18,7 @@ export enum NotePermissionLevel {
* Returns the display name for the given {@link NotePermissionLevel}. * Returns the display name for the given {@link NotePermissionLevel}.
* *
* @param {NotePermissionLevel} value the note permission to display * @param {NotePermissionLevel} value the note permission to display
* @return {string} The display name * @returns The display name
*/ */
export function getNotePermissionLevelDisplayName( export function getNotePermissionLevelDisplayName(
value: NotePermissionLevel, value: NotePermissionLevel,

View file

@ -90,7 +90,7 @@ export class PermissionService {
* Checks if the given {@link User} is allowed to create notes. * Checks if the given {@link User} is allowed to create notes.
* *
* @param username - The user whose permission should be checked. Value is null if guest access should be checked * @param username - The user whose permission should be checked. Value is null if guest access should be checked
* @return if the user is allowed to create notes * @returns if the user is allowed to create notes
*/ */
public mayCreate(username: string | null): boolean { public mayCreate(username: string | null): boolean {
return ( return (
@ -105,7 +105,7 @@ export class PermissionService {
* @param userId The id of the user * @param userId The id of the user
* @param noteId The id of the note * @param noteId The id of the note
* @param transaction Optional transaction to use * @param transaction Optional transaction to use
* @return true if the user is the owner of the note * @returns true if the user is the owner of the note
*/ */
async isOwner( async isOwner(
userId: number | null, userId: number | null,
@ -135,7 +135,7 @@ export class PermissionService {
* *
* @param {number | null} userId The user whose permission should be checked * @param {number | null} userId The user whose permission should be checked
* @param {number} noteId The note that is accessed by the given user * @param {number} noteId The note that is accessed by the given user
* @return {Promise<NotePermissionLevel>} The determined permission * @returns {Promise<NotePermissionLevel>} The determined permission
*/ */
public async determinePermission( public async determinePermission(
userId: number, userId: number,
@ -284,7 +284,7 @@ export class PermissionService {
* @param noteId the note * @param noteId the note
* @param userId the user for which the permission should be set * @param userId the user for which the permission should be set
* @param canEdit specifies if the user can edit the note * @param canEdit specifies if the user can edit the note
* @return the note with the new permission * @returns the note with the new permission
*/ */
async setUserPermission( async setUserPermission(
noteId: number, noteId: number,
@ -361,7 +361,7 @@ export class PermissionService {
* Remove permission for a specific group on a note. * Remove permission for a specific group on a note.
* @param noteId - the note * @param noteId - the note
* @param groupId - the group for which the permission should be set * @param groupId - the group for which the permission should be set
* @return the note with the new permission * @returns the note with the new permission
*/ */
async removeGroupPermission(noteId: number, groupId: number): Promise<void> { async removeGroupPermission(noteId: number, groupId: number): Promise<void> {
const result = await this.knex(TableNoteGroupPermission) const result = await this.knex(TableNoteGroupPermission)
@ -382,7 +382,7 @@ export class PermissionService {
* Updates the owner of a note. * Updates the owner of a note.
* @param noteId - the note to use * @param noteId - the note to use
* @param newOwnerId - the new owner * @param newOwnerId - the new owner
* @return the updated note * @returns the updated note
*/ */
async changeOwner(noteId: number, newOwnerId: number): Promise<void> { async changeOwner(noteId: number, newOwnerId: number): Promise<void> {
const result = await this.knex(TableNote) const result = await this.knex(TableNote)

View file

@ -11,7 +11,7 @@ import { NotePermissionLevel } from '../note-permission.enum';
* Converts the given guest access level to the highest possible {@link NotePermissionLevel}. * Converts the given guest access level to the highest possible {@link NotePermissionLevel}.
* *
* @param guestAccess the guest access level to should be converted * @param guestAccess the guest access level to should be converted
* @return the {@link NotePermissionLevel} representation * @returns the {@link NotePermissionLevel} representation
*/ */
export function convertPermissionLevelToNotePermissionLevel( export function convertPermissionLevelToNotePermissionLevel(
guestAccess: PermissionLevel, guestAccess: PermissionLevel,

View file

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
* *
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -8,7 +8,7 @@ import { adjectives, items } from './random-words';
/** /**
* Generates a random names based on an adjective and a noun. * Generates a random names based on an adjective and a noun.
* *
* @return the generated name * @returns the generated name
*/ */
export function generateRandomName(): string { export function generateRandomName(): string {
const adjective = generateRandomWord(adjectives); const adjective = generateRandomWord(adjectives);

View file

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
* *
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -18,7 +18,7 @@ export class RealtimeNoteStore {
* @param initialTextContent the initial text content of realtime doc * @param initialTextContent the initial text content of realtime doc
* @param initialYjsState the initial yjs state. If provided this will be used instead of the text content * @param initialYjsState the initial yjs state. If provided this will be used instead of the text content
* @throws Error if there is already an realtime note for the given note. * @throws Error if there is already an realtime note for the given note.
* @return The created realtime note * @returns The created realtime note
*/ */
public create( public create(
noteId: number, noteId: number,
@ -43,7 +43,7 @@ export class RealtimeNoteStore {
/** /**
* Retrieves a {@link RealtimeNote} that is linked to the given {@link Note} id. * Retrieves a {@link RealtimeNote} that is linked to the given {@link Note} id.
* @param noteId The id of the {@link Note} * @param noteId The id of the {@link Note}
* @return A {@link RealtimeNote} or {@code undefined} if no instance is existing. * @returns A {@link RealtimeNote} or {@code undefined} if no instance is existing.
*/ */
public find(noteId: number): RealtimeNote | undefined { public find(noteId: number): RealtimeNote | undefined {
return this.noteIdToRealtimeNote.get(noteId); return this.noteIdToRealtimeNote.get(noteId);

View file

@ -61,7 +61,7 @@ export class RealtimeNoteService implements BeforeApplicationShutdown {
* Creates or reuses a {@link RealtimeNote} that is handling the real time editing of the {@link Note} which is identified by the given note id. * Creates or reuses a {@link RealtimeNote} that is handling the real time editing of the {@link Note} which is identified by the given note id.
* @param noteId The {@link Note} for which a {@link RealtimeNote realtime note} should be retrieved. * @param noteId The {@link Note} for which a {@link RealtimeNote realtime note} should be retrieved.
* @throws NotInDBError if note doesn't exist or has no revisions. * @throws NotInDBError if note doesn't exist or has no revisions.
* @return A {@link RealtimeNote} that is linked to the given note. * @returns A {@link RealtimeNote} that is linked to the given note.
*/ */
public async getOrCreateRealtimeNote(noteId: number): Promise<RealtimeNote> { public async getOrCreateRealtimeNote(noteId: number): Promise<RealtimeNote> {
return ( return (
@ -75,7 +75,7 @@ export class RealtimeNoteService implements BeforeApplicationShutdown {
* *
* @param noteId The note for which the realtime note should be created * @param noteId The note for which the realtime note should be created
* @throws NotInDBError if note doesn't exist or has no revisions. * @throws NotInDBError if note doesn't exist or has no revisions.
* @return The created realtime note * @returns The created realtime note
*/ */
private async createNewRealtimeNote(noteId: number): Promise<RealtimeNote> { private async createNewRealtimeNote(noteId: number): Promise<RealtimeNote> {
const lastRevision = await this.revisionsService.getLatestRevision(noteId); const lastRevision = await this.revisionsService.getLatestRevision(noteId);

View file

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file) * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
* *
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -114,7 +114,7 @@ export class RealtimeNote extends EventEmitter2<RealtimeNoteEventMap> {
/** /**
* Checks if there's still clients connected to this note. * Checks if there's still clients connected to this note.
* *
* @return {@code true} if there a still clinets connected, otherwise {@code false} * @returns {@code true} if there a still clinets connected, otherwise {@code false}
*/ */
public hasConnections(): boolean { public hasConnections(): boolean {
return this.clients.size !== 0; return this.clients.size !== 0;
@ -123,7 +123,7 @@ export class RealtimeNote extends EventEmitter2<RealtimeNoteEventMap> {
/** /**
* Returns all {@link RealtimeConnection WebsocketConnections} currently hold by this note. * Returns all {@link RealtimeConnection WebsocketConnections} currently hold by this note.
* *
* @return an array of {@link RealtimeConnection WebsocketConnections} * @returns an array of {@link RealtimeConnection WebsocketConnections}
*/ */
public getConnections(): RealtimeConnection[] { public getConnections(): RealtimeConnection[] {
return [...this.clients]; return [...this.clients];
@ -132,7 +132,7 @@ export class RealtimeNote extends EventEmitter2<RealtimeNoteEventMap> {
/** /**
* Get the {@link RealtimeDoc realtime note} of the note. * Get the {@link RealtimeDoc realtime note} of the note.
* *
* @return the {@link RealtimeDoc realtime note} of the note * @returns the {@link RealtimeDoc realtime note} of the note
*/ */
public getRealtimeDoc(): RealtimeDoc { public getRealtimeDoc(): RealtimeDoc {
return this.doc; return this.doc;
@ -141,7 +141,7 @@ export class RealtimeNote extends EventEmitter2<RealtimeNoteEventMap> {
/** /**
* Get the {@link Note note} that is edited. * Get the {@link Note note} that is edited.
* *
* @return the {@link Note note} * @returns the {@link Note note}
*/ */
public getNoteId(): number { public getNoteId(): number {
return this.noteId; return this.noteId;

View file

@ -79,7 +79,7 @@ export class MockConnectionBuilder {
/** /**
* Creates a new connection based on the given configuration. * Creates a new connection based on the given configuration.
* *
* @return {RealtimeConnection} The constructed mocked connection * @returns {RealtimeConnection} The constructed mocked connection
* @throws Error if neither withGuestUser nor withLoggedInUser has been called. * @throws Error if neither withGuestUser nor withLoggedInUser has been called.
*/ */
public build(): RealtimeConnection { public build(): RealtimeConnection {

View file

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
* *
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -9,7 +9,7 @@ import { IncomingMessage } from 'http';
* Extracts the note id from the url of the given request. * Extracts the note id from the url of the given request.
* *
* @param request The request whose URL should be extracted * @param request The request whose URL should be extracted
* @return The extracted note id * @returns The extracted note id
* @throws Error if the given string isn't a valid realtime URL path * @throws Error if the given string isn't a valid realtime URL path
*/ */
export function extractNoteAliasFromRequestUrl( export function extractNoteAliasFromRequestUrl(

View file

@ -118,7 +118,7 @@ export class WebsocketGateway implements OnGatewayConnection {
* Finds the user id whose session cookie is saved in the given {@link IncomingMessage}. * Finds the user id whose session cookie is saved in the given {@link IncomingMessage}.
* *
* @param request The request that contains the session cookie * @param request The request that contains the session cookie
* @return The found user id * @returns The found user id
*/ */
private async findUserIdByRequestSession( private async findUserIdByRequestSession(
request: IncomingMessage, request: IncomingMessage,

View file

@ -59,7 +59,7 @@ export class RevisionsService {
* Returns all revisions of a note * Returns all revisions of a note
* *
* @param noteId The id of the note * @param noteId The id of the note
* @return The list of revisions * @returns The list of revisions
*/ */
async getAllRevisionMetadataDto( async getAllRevisionMetadataDto(
noteId: number, noteId: number,
@ -294,14 +294,14 @@ export class RevisionsService {
* Creates (but does not persist(!)) a new {@link Revision} for the given {@link Note}. * Creates (but does not persist(!)) a new {@link Revision} for the given {@link Note}.
* Useful if the revision is saved together with the note in one action. * Useful if the revision is saved together with the note in one action.
* *
* @async *
* @param noteId The note for which the revision should be created * @param noteId The note for which the revision should be created
* @param newContent The new note content * @param newContent The new note content
* @param firstRevision Whether this is called for the first revision of a note * @param firstRevision Whether this is called for the first revision of a note
* @param transaction The optional pre-existing database transaction to use * @param transaction The optional pre-existing database transaction to use
* @param yjsStateVector The yjs state vector that describes the new content * @param yjsStateVector The yjs state vector that describes the new content
* @return {Revision} the created revision * @returns {Revision} the created revision
* @return {undefined} if the revision couldn't be created because e.g. the content hasn't changed * @returns {undefined} if the revision couldn't be created because e.g. the content hasn't changed
*/ */
async createRevision( async createRevision(
noteId: number, noteId: number,

View file

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file) * SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
* *
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -31,7 +31,7 @@ interface FrontmatterParserResult {
/** /**
* Parses the frontmatter of the given content and extracts the metadata that are necessary to create a new revision.. * Parses the frontmatter of the given content and extracts the metadata that are necessary to create a new revision..
* *
* @param {string} content the revision content that contains the frontmatter. * @param content the revision content that contains the frontmatter.
*/ */
export function extractRevisionMetadataFromContent( export function extractRevisionMetadataFromContent(
content: string, content: string,

View file

@ -40,7 +40,7 @@ export class SessionService {
* Returns the currently used session store for usage outside of the HTTP session context * Returns the currently used session store for usage outside of the HTTP session context
* Note that this method is also used for connecting the session store with NestJS initially * Note that this method is also used for connecting the session store with NestJS initially
* *
* @return The used session store * @returns The used session store
*/ */
getSessionStore(): KeyvSessionStore<SessionState> { getSessionStore(): KeyvSessionStore<SessionState> {
return this.sessionStore; return this.sessionStore;
@ -50,7 +50,7 @@ export class SessionService {
* Finds the username of the user that has the given session id * Finds the username of the user that has the given session id
* *
* @param sessionId The session id for which the owning user should be found * @param sessionId The session id for which the owning user should be found
* @return A Promise that either resolves with the username or rejects with an error * @returns A Promise that either resolves with the username or rejects with an error
*/ */
async getUserIdForSessionId( async getUserIdForSessionId(
sessionId: string, sessionId: string,
@ -63,7 +63,7 @@ export class SessionService {
* Extracts the hedgedoc session cookie from the given {@link IncomingMessage request} and checks if the signature is correct. * Extracts the hedgedoc session cookie from the given {@link IncomingMessage request} and checks if the signature is correct.
* *
* @param request The http request that contains a session cookie * @param request The http request that contains a session cookie
* @return An {@link Optional optional} that either contains the extracted session id or is empty if no session cookie has been found * @returns An {@link Optional optional} that either contains the extracted session id or is empty if no session cookie has been found
* @throws Error if the cookie has been found but the content is malformed * @throws Error if the cookie has been found but the content is malformed
* @throws Error if the cookie has been found but the content isn't signed * @throws Error if the cookie has been found but the content isn't signed
*/ */
@ -79,7 +79,7 @@ export class SessionService {
* Parses the given session cookie content and extracts the session id * Parses the given session cookie content and extracts the session id
* *
* @param rawCookie The cookie to parse * @param rawCookie The cookie to parse
* @return The extracted session id * @returns The extracted session id
* @throws Error if the cookie has been found but the content is malformed * @throws Error if the cookie has been found but the content is malformed
* @throws Error if the cookie has been found but the content isn't signed * @throws Error if the cookie has been found but the content isn't signed
*/ */

View file

@ -44,7 +44,7 @@ export class UsersService {
* @param [email] New user's email address if exists * @param [email] New user's email address if exists
* @param [photoUrl] URL of the user's profile picture if exists * @param [photoUrl] URL of the user's profile picture if exists
* @param transaction The optional transaction to access the db * @param transaction The optional transaction to access the db
* @return The id of newly created user * @returns The id of newly created user
* @throws {BadRequestException} if the username contains invalid characters or is too short * @throws {BadRequestException} if the username contains invalid characters or is too short
* @throws {AlreadyInDBError} the username is already taken. * @throws {AlreadyInDBError} the username is already taken.
* @thorws {GenericDBError} the database returned a non-expected value * @thorws {GenericDBError} the database returned a non-expected value
@ -96,7 +96,7 @@ export class UsersService {
/** /**
* Creates a new guest user with a random displayName * Creates a new guest user with a random displayName
* *
* @return The guest uuid and the id of the newly created user * @returns The guest uuid and the id of the newly created user
* @throws {GenericDBError} the database returned a non-expected value * @throws {GenericDBError} the database returned a non-expected value
*/ */
async createGuestUser(): Promise<[string, number]> { async createGuestUser(): Promise<[string, number]> {
@ -195,7 +195,7 @@ export class UsersService {
* Checks if a given username is already taken * Checks if a given username is already taken
* *
* @param username The username to check * @param username The username to check
* @return true if the user exists, false otherwise * @returns true if the user exists, false otherwise
*/ */
async isUsernameTaken(username: string): Promise<boolean> { async isUsernameTaken(username: string): Promise<boolean> {
const result = await this.knex(TableUser) const result = await this.knex(TableUser)
@ -209,7 +209,7 @@ export class UsersService {
* *
* @param userId The id of the user to check * @param userId The id of the user to check
* @param transaction the optional transaction to access the db * @param transaction the optional transaction to access the db
* @return true if the user is registered, false otherwise * @returns true if the user is registered, false otherwise
*/ */
async isRegisteredUser( async isRegisteredUser(
userId: User[FieldNameUser.id], userId: User[FieldNameUser.id],
@ -228,7 +228,7 @@ export class UsersService {
* Fetches the userId for a given username from the database * Fetches the userId for a given username from the database
* *
* @param username The username to fetch * @param username The username to fetch
* @return The found user object * @returns The found user object
* @throws {NotInDBError} if the user could not be found * @throws {NotInDBError} if the user could not be found
*/ */
async getUserIdByUsername(username: string): Promise<number> { async getUserIdByUsername(username: string): Promise<number> {
@ -250,7 +250,7 @@ export class UsersService {
* Fetches the userId for a given username from the database * Fetches the userId for a given username from the database
* *
* @param uuid The uuid to fetch * @param uuid The uuid to fetch
* @return The found user object * @returns The found user object
* @throws {NotInDBError} if the user could not be found * @throws {NotInDBError} if the user could not be found
*/ */
async getUserIdByGuestUuid(uuid: string): Promise<User[FieldNameUser.id]> { async getUserIdByGuestUuid(uuid: string): Promise<User[FieldNameUser.id]> {
@ -272,7 +272,7 @@ export class UsersService {
* Fetches the user object for a given username from the database * Fetches the user object for a given username from the database
* *
* @param username The username to fetch * @param username The username to fetch
* @return The found user object * @returns The found user object
* @throws {NotInDBError} if the user could not be found * @throws {NotInDBError} if the user could not be found
*/ */
async getUserDtoByUsername(username: string): Promise<UserInfoDto> { async getUserDtoByUsername(username: string): Promise<UserInfoDto> {
@ -295,7 +295,7 @@ export class UsersService {
* Fetches the user object for a given username from the database * Fetches the user object for a given username from the database
* *
* @param userId The username to fetch * @param userId The username to fetch
* @return The found user object * @returns The found user object
* @throws {NotInDBError} if the user could not be found * @throws {NotInDBError} if the user could not be found
*/ */
async getUserById(userId: number): Promise<User> { async getUserById(userId: number): Promise<User> {
@ -318,7 +318,7 @@ export class UsersService {
* @param username The username to use as a seed when generating a random avatar * @param username The username to use as a seed when generating a random avatar
* @param email The email address of the user for using livbravatar if configured * @param email The email address of the user for using livbravatar if configured
* @param photoUrl The user-provided photo URL * @param photoUrl The user-provided photo URL
* @return A URL to the user's profile picture. * @returns A URL to the user's profile picture.
*/ */
private generatePhotoUrl( private generatePhotoUrl(
username: string, username: string,
@ -343,7 +343,7 @@ export class UsersService {
* Creates a random author style index based on a hashing of the username * Creates a random author style index based on a hashing of the username
* *
* @param username The username is used as input for the hash * @param username The username is used as input for the hash
* @return An index between 0 and 8 (including 0 and 8) * @returns An index between 0 and 8 (including 0 and 8)
*/ */
private generateAuthorStyleIndex(username: string): number { private generateAuthorStyleIndex(username: string): number {
let hash = 0; let hash = 0;
@ -358,7 +358,7 @@ export class UsersService {
* *
* @param user The user to fetch their data for * @param user The user to fetch their data for
* @param authProvider The auth provider used for the current login session * @param authProvider The auth provider used for the current login session
* @return The built OwnUserInfoDto * @returns The built OwnUserInfoDto
*/ */
toLoginUserInfoDto( toLoginUserInfoDto(
user: User, user: User,

View file

@ -13,7 +13,7 @@ let versionCache: ServerVersionDto | undefined = undefined;
/** /**
* Reads the HedgeDoc version from the root package.json. This is done only once per run. * Reads the HedgeDoc version from the root package.json. This is done only once per run.
* *
* @return {Promise<ServerVersionDto>} A Promise that contains the parsed server version. * @returns {Promise<ServerVersionDto>} A Promise that contains the parsed server version.
* @throws {Error} if the package.json couldn't be found or doesn't contain a correct version. * @throws {Error} if the package.json couldn't be found or doesn't contain a correct version.
*/ */
export async function getServerVersionFromPackageJson(): Promise<ServerVersionDto> { export async function getServerVersionFromPackageJson(): Promise<ServerVersionDto> {

View file

@ -414,17 +414,20 @@ export class TestSetupBuilder {
); );
// Create identities for login // Create identities for login
await this.testSetup.localIdentityService.createLocalIdentity( await this.testSetup.localIdentityService.createUserWithLocalIdentity(
this.testSetup.users[0], this.testSetup.users[0],
password1, password1,
'',
); );
await this.testSetup.localIdentityService.createLocalIdentity( await this.testSetup.localIdentityService.createUserWithLocalIdentity(
this.testSetup.users[1], this.testSetup.users[1],
password2, password2,
'',
); );
await this.testSetup.localIdentityService.createLocalIdentity( await this.testSetup.localIdentityService.createUserWithLocalIdentity(
this.testSetup.users[2], this.testSetup.users[2],
password3, password3,
'',
); );
// create auth tokens // create auth tokens