mirror of
https://github.com/bewcloud/bewcloud.git
synced 2026-03-11 08:54:49 +00:00
Compare commits
4 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f871b72ad | ||
|
|
f50423028b | ||
|
|
69a916d709 | ||
|
|
0d20b4a337 |
11 changed files with 51 additions and 155 deletions
|
|
@ -11,6 +11,7 @@ const config: PartialDeep<Config> = {
|
|||
// allowedCookieDomains: ['example.com', 'example.net'], // Can be set to allow more than the baseUrl's domain for session cookies
|
||||
// skipCookieDomainSecurity: true, // If true, the cookie domain will not be strictly set and checked against. This skipping slightly reduces security, but is usually necessary for reverse proxies like Cloudflare Tunnel
|
||||
// enableSingleSignOn: false, // If true, single sign-on will be enabled
|
||||
// allowSignupsViaSingleSignOn: false, // If true, signups via single sign-on will be allowed, overriding allowSignups
|
||||
// singleSignOnUrl: '', // The Discovery URL (AKA Issuer) of the identity/single sign-on provider
|
||||
// singleSignOnEmailAttribute: 'email', // The attribute to prefer as email of the identity/single sign-on provider
|
||||
// singleSignOnScopes: ['openid', 'email'], // The scopes to request from the identity/single sign-on provider
|
||||
|
|
@ -19,9 +20,11 @@ const config: PartialDeep<Config> = {
|
|||
// rootPath: 'data-files',
|
||||
// allowPublicSharing: false, // If true, public file sharing will be allowed (still requires a user to enable sharing for a given file or directory)
|
||||
// allowDirectoryDownloads: false, // If true, directories can be downloaded as zip files
|
||||
// maxUploadSizeInMegabytes: 100, // The maximum upload size in megabytes. Overrides the core.maxRequestSizeInMegabytes setting on /dav and /api/files/upload endpoints.
|
||||
// },
|
||||
// core: {
|
||||
// enabledApps: ['dashboard', 'files', 'news', 'notes', 'photos', 'expenses', 'contacts', 'calendar'], // The apps to show, in order of appearance in the header. The first app will be the default one shown after logging in. At least one is required.
|
||||
// maxRequestSizeInMegabytes: 12, // The maximum request size in megabytes.
|
||||
// },
|
||||
// visuals: {
|
||||
// title: 'My own cloud',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
services:
|
||||
website:
|
||||
image: ghcr.io/bewcloud/bewcloud:v4.0.3
|
||||
image: ghcr.io/bewcloud/bewcloud:v4.1.2
|
||||
# NOTE: uncomment below (and comment above) only if you pulled the repo and want to build the image locally
|
||||
# build:
|
||||
# context: .
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export class AppConfig {
|
|||
allowedCookieDomains: [],
|
||||
skipCookieDomainSecurity: false,
|
||||
enableSingleSignOn: false,
|
||||
allowSignupsViaSingleSignOn: false,
|
||||
singleSignOnUrl: '',
|
||||
singleSignOnEmailAttribute: 'email',
|
||||
singleSignOnScopes: ['openid', 'email'],
|
||||
|
|
@ -24,9 +25,11 @@ export class AppConfig {
|
|||
rootPath: 'data-files',
|
||||
allowPublicSharing: false,
|
||||
allowDirectoryDownloads: false,
|
||||
maxUploadSizeInMegabytes: 100,
|
||||
},
|
||||
core: {
|
||||
enabledApps: ['dashboard', 'files', 'news', 'notes', 'photos', 'expenses', 'contacts', 'calendar'],
|
||||
maxRequestSizeInMegabytes: 12,
|
||||
},
|
||||
visuals: {
|
||||
title: '',
|
||||
|
|
@ -117,11 +120,12 @@ export class AppConfig {
|
|||
return this.config;
|
||||
}
|
||||
|
||||
static async isSignupAllowed(): Promise<boolean> {
|
||||
static async isSignupAllowed({ viaSingleSignOn = false }: { viaSingleSignOn?: boolean } = {}): Promise<boolean> {
|
||||
await this.loadConfig();
|
||||
|
||||
const areSignupsAllowed = this.config.auth.allowSignups;
|
||||
|
||||
const areSignupsAllowed = viaSingleSignOn && !this.config.auth.allowSignups
|
||||
? this.config.auth.allowSignupsViaSingleSignOn
|
||||
: this.config.auth.allowSignups;
|
||||
const areThereAdmins = await UserModel.isThereAnAdmin();
|
||||
|
||||
if (areSignupsAllowed || !areThereAdmins) {
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ export class OidcModel {
|
|||
throw new Error(`Missing user/${emailAttribute}`);
|
||||
}
|
||||
|
||||
const isSignupAllowed = await AppConfig.isSignupAllowed();
|
||||
const isSignupAllowed = await AppConfig.isSignupAllowed({ viaSingleSignOn: true });
|
||||
const isThereAnAdmin = await UserModel.isThereAnAdmin();
|
||||
|
||||
// Confirm the user exists (or signup if allowed)
|
||||
|
|
@ -181,6 +181,10 @@ export class OidcModel {
|
|||
}
|
||||
|
||||
if (!user) {
|
||||
if (!config.auth.allowSignupsViaSingleSignOn) {
|
||||
throw new Error('Sign up via SSO is not allowed!');
|
||||
}
|
||||
|
||||
throw new Error('There was a problem signing up or logging in!');
|
||||
}
|
||||
|
||||
|
|
|
|||
30
lib/page.ts
30
lib/page.ts
|
|
@ -17,15 +17,7 @@ export interface Page {
|
|||
patch?: RequestHandler;
|
||||
delete?: RequestHandler;
|
||||
options?: RequestHandler;
|
||||
copy?: RequestHandler;
|
||||
move?: RequestHandler;
|
||||
mkcol?: RequestHandler;
|
||||
mkcalendar?: RequestHandler;
|
||||
lock?: RequestHandler;
|
||||
unlock?: RequestHandler;
|
||||
propfind?: RequestHandler;
|
||||
proppatch?: RequestHandler;
|
||||
report?: RequestHandler;
|
||||
catchAll?: RequestHandler;
|
||||
}
|
||||
|
||||
type AccessMode = 'public' | 'user';
|
||||
|
|
@ -61,15 +53,7 @@ export default function page(
|
|||
patch,
|
||||
delete: deleteAction,
|
||||
options,
|
||||
copy,
|
||||
move,
|
||||
mkcol,
|
||||
mkcalendar,
|
||||
lock,
|
||||
unlock,
|
||||
propfind,
|
||||
proppatch,
|
||||
report,
|
||||
catchAll,
|
||||
accessMode,
|
||||
}: Params,
|
||||
): Page {
|
||||
|
|
@ -80,14 +64,6 @@ export default function page(
|
|||
patch: patch ? permissioned(patch, accessMode) : undefined,
|
||||
delete: deleteAction ? permissioned(deleteAction, accessMode) : undefined,
|
||||
options: options ? permissioned(options, accessMode) : undefined,
|
||||
copy: copy ? permissioned(copy, accessMode) : undefined,
|
||||
move: move ? permissioned(move, accessMode) : undefined,
|
||||
mkcol: mkcol ? permissioned(mkcol, accessMode) : undefined,
|
||||
mkcalendar: mkcalendar ? permissioned(mkcalendar, accessMode) : undefined,
|
||||
lock: lock ? permissioned(lock, accessMode) : undefined,
|
||||
unlock: unlock ? permissioned(unlock, accessMode) : undefined,
|
||||
propfind: propfind ? permissioned(propfind, accessMode) : undefined,
|
||||
proppatch: proppatch ? permissioned(proppatch, accessMode) : undefined,
|
||||
report: report ? permissioned(report, accessMode) : undefined,
|
||||
catchAll: catchAll ? permissioned(catchAll, accessMode) : undefined,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,6 +159,8 @@ export interface Config {
|
|||
skipCookieDomainSecurity: boolean;
|
||||
/** If true, single sign-on will be enabled */
|
||||
enableSingleSignOn: boolean;
|
||||
/** If true, signups via single sign-on will be allowed, overriding allowSignups */
|
||||
allowSignupsViaSingleSignOn: boolean;
|
||||
/** The Discovery URL (AKA Issuer) of the identity/single sign-on provider */
|
||||
singleSignOnUrl: string;
|
||||
/** The attribute to prefer as email of the identity/single sign-on provider */
|
||||
|
|
@ -173,10 +175,14 @@ export interface Config {
|
|||
allowPublicSharing: boolean;
|
||||
/** If true, directories can be downloaded as zip files */
|
||||
allowDirectoryDownloads: boolean;
|
||||
/** The maximum upload size in megabytes. Overrides the core.maxRequestSizeInMegabytes setting on /dav and /api/files/upload endpoints. */
|
||||
maxUploadSizeInMegabytes: number;
|
||||
};
|
||||
core: {
|
||||
/** The apps to show, in order of appearance in the header. The first app will be the default one shown after logging in. At least one is required. */
|
||||
enabledApps: OptionalApp[];
|
||||
/** The maximum request size in megabytes. */
|
||||
maxRequestSizeInMegabytes: number;
|
||||
};
|
||||
visuals: {
|
||||
/** An override title of the application. Empty shows the default title. */
|
||||
|
|
|
|||
13
main.ts
13
main.ts
|
|
@ -1,9 +1,13 @@
|
|||
import routes, { Route } from './routes.ts';
|
||||
import { startCrons } from './crons/index.ts';
|
||||
import { Page } from './lib/page.ts';
|
||||
import { AppConfig } from './lib/config.ts';
|
||||
|
||||
const MAX_REQUEST_SIZE_IN_MEGABYTES = 12;
|
||||
const config = await AppConfig.getConfig();
|
||||
const MAX_REQUEST_SIZE_IN_MEGABYTES = config.core.maxRequestSizeInMegabytes;
|
||||
const MAX_UPLOAD_SIZE_IN_MEGABYTES = config.files.maxUploadSizeInMegabytes;
|
||||
const MAX_REQUEST_SIZE_IN_BYTES = MAX_REQUEST_SIZE_IN_MEGABYTES * 1024 * 1024;
|
||||
const MAX_UPLOAD_SIZE_IN_BYTES = MAX_UPLOAD_SIZE_IN_MEGABYTES * 1024 * 1024;
|
||||
|
||||
function applyCorsHeadersToResponse(origin: string, response: Response) {
|
||||
const headers = response.headers;
|
||||
|
|
@ -53,12 +57,15 @@ function handleLogging(request: Request, response: Response) {
|
|||
|
||||
async function handler(request: Request) {
|
||||
const contentLength = request.headers.get('content-length');
|
||||
const path = new URL(request.url).pathname;
|
||||
|
||||
if (contentLength && parseInt(contentLength, 10) > MAX_REQUEST_SIZE_IN_BYTES) {
|
||||
const isUploadRequest = path.startsWith('/api/files/upload') || path.startsWith('/dav');
|
||||
const maxSizeInBytes = isUploadRequest ? MAX_UPLOAD_SIZE_IN_BYTES : MAX_REQUEST_SIZE_IN_BYTES;
|
||||
|
||||
if (contentLength && parseInt(contentLength, 10) > maxSizeInBytes) {
|
||||
return new Response('Payload too large', { status: 413 });
|
||||
}
|
||||
|
||||
const path = new URL(request.url).pathname;
|
||||
const origin = request.headers.get('Origin') || '*';
|
||||
|
||||
// CORS headers for non-DAV routes
|
||||
|
|
|
|||
|
|
@ -75,14 +75,6 @@ export default page({
|
|||
put: get,
|
||||
delete: get,
|
||||
options: get,
|
||||
copy: get,
|
||||
move: get,
|
||||
mkcol: get,
|
||||
mkcalendar: get,
|
||||
lock: get,
|
||||
unlock: get,
|
||||
propfind: get,
|
||||
proppatch: get,
|
||||
report: get,
|
||||
catchAll: get,
|
||||
accessMode: 'public',
|
||||
});
|
||||
|
|
|
|||
|
|
@ -75,13 +75,6 @@ export default page({
|
|||
put: get,
|
||||
delete: get,
|
||||
options: get,
|
||||
copy: get,
|
||||
move: get,
|
||||
mkcol: get,
|
||||
lock: get,
|
||||
unlock: get,
|
||||
propfind: get,
|
||||
proppatch: get,
|
||||
report: get,
|
||||
catchAll: get,
|
||||
accessMode: 'public',
|
||||
});
|
||||
|
|
|
|||
10
pages/dav.ts
10
pages/dav.ts
|
|
@ -142,6 +142,8 @@ async function handler({ request, user, match }: RequestHandlerParams): Promise<
|
|||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return new Response('Not Found', { status: 404 });
|
||||
}
|
||||
|
||||
if (request.method === 'MKCOL') {
|
||||
|
|
@ -257,12 +259,6 @@ export default page({
|
|||
delete: handler,
|
||||
put: handler,
|
||||
options: handler,
|
||||
copy: handler,
|
||||
move: handler,
|
||||
mkcol: handler,
|
||||
lock: handler,
|
||||
unlock: handler,
|
||||
propfind: handler,
|
||||
report: handler,
|
||||
catchAll: handler,
|
||||
accessMode: 'public',
|
||||
});
|
||||
|
|
|
|||
107
routes.ts
107
routes.ts
|
|
@ -33,14 +33,7 @@ function createPageRouteHandler(id: string, pathname: string) {
|
|||
patch,
|
||||
delete: deleteAction,
|
||||
options,
|
||||
copy,
|
||||
move,
|
||||
mkcol,
|
||||
lock,
|
||||
unlock,
|
||||
propfind,
|
||||
proppatch,
|
||||
report,
|
||||
catchAll,
|
||||
} = page;
|
||||
|
||||
const { user, session, tokenData } = (await getDataFromRequest(request)) || {};
|
||||
|
|
@ -148,95 +141,17 @@ function createPageRouteHandler(id: string, pathname: string) {
|
|||
});
|
||||
}
|
||||
break;
|
||||
case 'COPY':
|
||||
if (copy) {
|
||||
return await copy({
|
||||
request,
|
||||
match,
|
||||
user,
|
||||
session: { userSession: session, tokenData },
|
||||
isRunningLocally,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'MOVE':
|
||||
if (move) {
|
||||
return await move({
|
||||
request,
|
||||
match,
|
||||
user,
|
||||
session: { userSession: session, tokenData },
|
||||
isRunningLocally,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'MKCOL':
|
||||
if (mkcol) {
|
||||
return await mkcol({
|
||||
request,
|
||||
match,
|
||||
user,
|
||||
session: { userSession: session, tokenData },
|
||||
isRunningLocally,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'LOCK':
|
||||
if (lock) {
|
||||
return await lock({
|
||||
request,
|
||||
match,
|
||||
user,
|
||||
session: { userSession: session, tokenData },
|
||||
isRunningLocally,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'UNLOCK':
|
||||
if (unlock) {
|
||||
return await unlock({
|
||||
request,
|
||||
match,
|
||||
user,
|
||||
session: { userSession: session, tokenData },
|
||||
isRunningLocally,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'PROPFIND':
|
||||
if (propfind) {
|
||||
return await propfind({
|
||||
request,
|
||||
match,
|
||||
user,
|
||||
session: { userSession: session, tokenData },
|
||||
isRunningLocally,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'PROPPATCH':
|
||||
if (proppatch) {
|
||||
return await proppatch({
|
||||
request,
|
||||
match,
|
||||
user,
|
||||
session: { userSession: session, tokenData },
|
||||
isRunningLocally,
|
||||
});
|
||||
}
|
||||
break;
|
||||
case 'REPORT':
|
||||
if (report) {
|
||||
return await report({
|
||||
request,
|
||||
match,
|
||||
user,
|
||||
session: { userSession: session, tokenData },
|
||||
isRunningLocally,
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (catchAll) {
|
||||
return await catchAll({
|
||||
request,
|
||||
match,
|
||||
user,
|
||||
session: { userSession: session, tokenData },
|
||||
isRunningLocally,
|
||||
});
|
||||
}
|
||||
|
||||
return new Response('Not Implemented', { status: 501 });
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue