mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-23 03:27:05 -04:00
refactor: reorganize files in commons package
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
c9b98f6185
commit
4d9792bcb9
23 changed files with 94 additions and 69 deletions
|
@ -1,17 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class MissingTrailingSlashError extends Error {
|
||||
constructor() {
|
||||
super("Path doesn't end with a trailing slash")
|
||||
}
|
||||
}
|
||||
|
||||
export class WrongProtocolError extends Error {
|
||||
constructor() {
|
||||
super('Protocol must be HTTP or HTTPS')
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { MissingTrailingSlashError, WrongProtocolError } from './errors.js'
|
||||
import { parseUrl } from './parse-url.js'
|
||||
import { describe, expect, it } from '@jest/globals'
|
||||
|
||||
describe('validate url', () => {
|
||||
it("doesn't accept non-urls", () => {
|
||||
expect(parseUrl('noUrl').isEmpty()).toBeTruthy()
|
||||
})
|
||||
|
||||
describe('protocols', () => {
|
||||
it('works with http', () => {
|
||||
expect(parseUrl('http://example.org').get().toString()).toEqual(
|
||||
'http://example.org/'
|
||||
)
|
||||
})
|
||||
it('works with https', () => {
|
||||
expect(parseUrl('https://example.org').get().toString()).toEqual(
|
||||
'https://example.org/'
|
||||
)
|
||||
})
|
||||
it("doesn't work without protocol", () => {
|
||||
expect(() => parseUrl('example.org').isEmpty()).toBeTruthy()
|
||||
})
|
||||
it("doesn't work any other protocol", () => {
|
||||
expect(() => parseUrl('git://example.org').get()).toThrowError(
|
||||
WrongProtocolError
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('trailing slash', () => {
|
||||
it('accepts urls with just domain with trailing slash', () => {
|
||||
expect(parseUrl('http://example.org/').get().toString()).toEqual(
|
||||
'http://example.org/'
|
||||
)
|
||||
})
|
||||
it('accepts urls with just domain without trailing slash', () => {
|
||||
expect(parseUrl('http://example.org').get().toString()).toEqual(
|
||||
'http://example.org/'
|
||||
)
|
||||
})
|
||||
it('accepts urls with with subpath and trailing slash', () => {
|
||||
expect(parseUrl('http://example.org/asd/').get().toString()).toEqual(
|
||||
'http://example.org/asd/'
|
||||
)
|
||||
})
|
||||
it("doesn't accept urls with with subpath and without trailing slash", () => {
|
||||
expect(() => parseUrl('http://example.org/asd').get().toString()).toThrow(
|
||||
MissingTrailingSlashError
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { MissingTrailingSlashError, WrongProtocolError } from './errors.js'
|
||||
import { Optional } from '@mrdrogdrog/optional'
|
||||
|
||||
/**
|
||||
* Parses the given string as URL
|
||||
*
|
||||
* @param {String | undefined} url the raw url
|
||||
* @return An {@link Optional} that contains the parsed URL or is empty if the raw value isn't a valid URL
|
||||
* @throws WrongProtocolError if the protocol of the URL isn't either http nor https
|
||||
* @throws MissingTrailingSlashError if the URL has a path that doesn't end with a trailing slash
|
||||
*/
|
||||
export function parseUrl(url: string | undefined): Optional<URL> {
|
||||
return createOptionalUrl(url)
|
||||
.guard(
|
||||
(value) => value.protocol === 'https:' || value.protocol === 'http:',
|
||||
() => new WrongProtocolError()
|
||||
)
|
||||
.guard(
|
||||
(value) => value.pathname.endsWith('/'),
|
||||
() => new MissingTrailingSlashError()
|
||||
)
|
||||
}
|
||||
|
||||
function createOptionalUrl(url: string | undefined): Optional<URL> {
|
||||
try {
|
||||
return Optional.ofNullable(url).map((value) => new URL(value))
|
||||
} catch (error) {
|
||||
return Optional.empty()
|
||||
}
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { userCanEdit, userIsOwner } from './permissions.js'
|
||||
import { NotePermissions, SpecialGroup } from './permissions.types.js'
|
||||
import { describe, expect, it } from '@jest/globals'
|
||||
|
||||
describe('Permissions', () => {
|
||||
const testPermissions: NotePermissions = {
|
||||
owner: 'owner',
|
||||
sharedToUsers: [
|
||||
{
|
||||
username: 'logged_in',
|
||||
canEdit: true
|
||||
}
|
||||
],
|
||||
sharedToGroups: [
|
||||
{
|
||||
groupName: SpecialGroup.EVERYONE,
|
||||
canEdit: true
|
||||
},
|
||||
{
|
||||
groupName: SpecialGroup.LOGGED_IN,
|
||||
canEdit: true
|
||||
}
|
||||
]
|
||||
}
|
||||
describe('userIsOwner', () => {
|
||||
it('returns true, if user is owner', () => {
|
||||
expect(userIsOwner(testPermissions, 'owner')).toBeTruthy()
|
||||
})
|
||||
it('returns false, if user is not ownerr', () => {
|
||||
expect(userIsOwner(testPermissions, 'not_owner')).toBeFalsy()
|
||||
})
|
||||
it('returns false, if user is undefined', () => {
|
||||
expect(userIsOwner(testPermissions, undefined)).toBeFalsy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('userCanEdit', () => {
|
||||
it('returns true, if user is owner', () => {
|
||||
expect(userCanEdit(testPermissions, 'owner')).toBeTruthy()
|
||||
})
|
||||
it('returns true, if user is logged in and this is user specifically may edit', () => {
|
||||
expect(
|
||||
userCanEdit({ ...testPermissions, sharedToGroups: [] }, 'logged_in')
|
||||
).toBeTruthy()
|
||||
})
|
||||
it('returns true, if user is logged in and loggedIn users may edit', () => {
|
||||
expect(
|
||||
userCanEdit({ ...testPermissions, sharedToUsers: [] }, 'logged_in')
|
||||
).toBeTruthy()
|
||||
})
|
||||
it('returns true, if user is guest and guests are allowed to edit', () => {
|
||||
expect(
|
||||
userCanEdit({ ...testPermissions, sharedToUsers: [] }, undefined)
|
||||
).toBeTruthy()
|
||||
})
|
||||
it('returns false, if user is logged in and loggedIn users may not edit', () => {
|
||||
expect(
|
||||
userCanEdit(
|
||||
{ ...testPermissions, sharedToUsers: [], sharedToGroups: [] },
|
||||
'logged_in'
|
||||
)
|
||||
).toBeFalsy()
|
||||
})
|
||||
it('returns false, if user is guest and guests are not allowed to edit', () => {
|
||||
expect(
|
||||
userCanEdit(
|
||||
{
|
||||
...testPermissions,
|
||||
sharedToUsers: [],
|
||||
sharedToGroups: [
|
||||
{
|
||||
groupName: SpecialGroup.LOGGED_IN,
|
||||
canEdit: true
|
||||
}
|
||||
]
|
||||
},
|
||||
undefined
|
||||
)
|
||||
).toBeFalsy()
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { NotePermissions, SpecialGroup } from './permissions.types.js'
|
||||
|
||||
/**
|
||||
* Checks if the given user is the owner of a note.
|
||||
*
|
||||
* @param permissions The permissions of the note to check
|
||||
* @param user The username of the user
|
||||
* @return True if the user is the owner of the note
|
||||
*/
|
||||
export const userIsOwner = (
|
||||
permissions: NotePermissions,
|
||||
user?: string
|
||||
): boolean => {
|
||||
return !!user && permissions.owner === user
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given user may edit a note.
|
||||
*
|
||||
* @param permissions The permissions of the note to check
|
||||
* @param user The username of the user
|
||||
* @return True if the user has the permission to edit the note
|
||||
*/
|
||||
export const userCanEdit = (
|
||||
permissions: NotePermissions,
|
||||
user?: string
|
||||
): boolean => {
|
||||
const isOwner = userIsOwner(permissions, user)
|
||||
const mayWriteViaUserPermission = permissions.sharedToUsers.some(
|
||||
(value) => value.canEdit && value.username === user
|
||||
)
|
||||
const mayWriteViaGroupPermission =
|
||||
!!user &&
|
||||
permissions.sharedToGroups.some(
|
||||
(value) => value.groupName === SpecialGroup.LOGGED_IN && value.canEdit
|
||||
)
|
||||
const everyoneMayWriteViaGroupPermission = permissions.sharedToGroups.some(
|
||||
(value) => value.groupName === SpecialGroup.EVERYONE && value.canEdit
|
||||
)
|
||||
return (
|
||||
isOwner ||
|
||||
mayWriteViaUserPermission ||
|
||||
mayWriteViaGroupPermission ||
|
||||
everyoneMayWriteViaGroupPermission
|
||||
)
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export interface NotePermissions {
|
||||
owner: string | null
|
||||
sharedToUsers: NoteUserPermissionEntry[]
|
||||
sharedToGroups: NoteGroupPermissionEntry[]
|
||||
}
|
||||
|
||||
export interface NoteUserPermissionEntry {
|
||||
username: string
|
||||
canEdit: boolean
|
||||
}
|
||||
|
||||
export interface NoteGroupPermissionEntry {
|
||||
groupName: string
|
||||
canEdit: boolean
|
||||
}
|
||||
export enum AccessLevel {
|
||||
NONE,
|
||||
READ_ONLY,
|
||||
WRITEABLE
|
||||
}
|
||||
|
||||
export enum SpecialGroup {
|
||||
EVERYONE = '_EVERYONE',
|
||||
LOGGED_IN = '_LOGGED_IN'
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/**
|
||||
* Waits until all other pending promises are processed.
|
||||
*
|
||||
* NodeJS has a queue for async code that waits for being processed. This method adds a promise to the very end of this queue.
|
||||
* If the promise is resolved then this means that all other promises before it have been processed as well.
|
||||
*
|
||||
* @return A promise which resolves when all other promises have been processed
|
||||
*/
|
||||
export function waitForOtherPromisesToFinish(): Promise<void> {
|
||||
return new Promise((resolve) => process.nextTick(resolve))
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue