after a fair bit of changes, it is now working
diff --git a/build/schemabuilder.js b/build/schemabuilder.js
index e7589bb..20722ec 100644
--- a/build/schemabuilder.js
+++ b/build/schemabuilder.js
@@ -7,13 +7,19 @@ const relativePath = f => path.resolve(__dirname, "..", f);
const tsconfig = relativePath("tsconfig.json");
-for(let {input, output, type} of schemas) {
+for (let { input, output, type } of schemas) {
+ console.log("Generating schema for:", { input, output, type });
const config = {
path: relativePath(input),
tsconfig,
type
};
- const schema = tsj.createGenerator(config).createSchema(config.type);
- const schemaString = JSON.stringify(schema, null, 2);
- fs.writeFileSync(relativePath(output), schemaString);
+ try {
+ const schema = tsj.createGenerator(config).createSchema(config.type);
+ const schemaString = JSON.stringify(schema, null, 2);
+ console.log(`Writing schema to ${relativePath(output)}`);
+ fs.writeFileSync(relativePath(output), schemaString);
+ } catch (error) {
+ console.error(`Error generating schema for ${type}:`, error);
+ }
}
diff --git a/etc/firestore.rules b/etc/firestore.rules
index abd5a65..e5675e7 100644
--- a/etc/firestore.rules
+++ b/etc/firestore.rules
@@ -13,10 +13,12 @@ service cloud.firestore {
function hasAccessByRole() {
// don't allow access to personal bags in this match clause
let isPersonalBag = bagName.matches('^user%3A.*');
- // anybody with at least reader can read non-personal bags
- let allowedByRole = get(/databases/$(database)/documents/wikis/$(wikiName)/users/$(request.auth.uid)).data.role >= 2;
+ // Verify role exists and is valid
+ let allowedByRole = exists(/databases/$(database)/documents/wikis/$(wikiName)/users/$(request.auth.uid)) &&
+ get(/databases/$(database)/documents/wikis/$(wikiName)/users/$(request.auth.uid)).data.role >= 2;
return !isPersonalBag && allowedByRole;
}
+
allow get, list: if request.auth != null && hasAccessByRole();
}
}
diff --git a/firebase.production.json b/firebase.production.json
index 9758800..2eaa7b1 100644
--- a/firebase.production.json
+++ b/firebase.production.json
@@ -7,7 +7,7 @@
},
"hosting": [
{
- "site": "pn-wiki",
+ "site": "kronako-wiki",
"public": "public",
"redirects": [{
"source": "/static/:file*",
diff --git a/package.json b/package.json
index ac5c55f..b1c1e93 100644
--- a/package.json
+++ b/package.json
@@ -58,11 +58,15 @@
"yargs": "^16.2.0"
},
"dependencies": {
+ "@js-sdsl/ordered-map": "^4.4.2",
+ "@types/helmet": "^0.0.48",
"ajv": "^7.2.1",
"cors": "^2.8.5",
"express": "^4.17.1",
+ "express-rate-limit": "^7.5.0",
"firebase-admin": "^9.5.0",
"firebase-functions": "^3.13.2",
+ "helmet": "^8.0.0",
"inversify": "^5.0.5",
"reflect-metadata": "^0.1.13",
"source-map-support": "^0.5.19"
diff --git a/src/backend/api/endpoints.ts b/src/backend/api/endpoints.ts
index 9a5d608..4540f34 100644
--- a/src/backend/api/endpoints.ts
+++ b/src/backend/api/endpoints.ts
@@ -20,7 +20,7 @@
*/
import cors from 'cors';
-import * as express from 'express';
+import express from 'express';
import { inject, injectable } from 'inversify';
import { TW5FirebaseError, TW5FirebaseErrorCode } from '../../shared/model/errors';
import { SingleWikiNamespacedTiddler } from '../../shared/model/store';
@@ -160,18 +160,61 @@ export class APIEndpointFactory {
}
createAPI() {
- const api = express.default();
- api.use(cors({ origin: true }));
+ const api = express();
+
+ // CORS middleware
+ api.use(cors({
+ origin: 'https://kronako-wiki.web.app',
+ methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
+ allowedHeaders: 'Authorization, Content-Type, Accept',
+ optionsSuccessStatus: 204
+ }));
+
+ // Preflight request handling
+ api.options('*', (req, res) => {
+ res.set({
+ 'Access-Control-Allow-Origin': 'https://kronako-wiki.web.app',
+ 'Access-Control-Allow-Methods': 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
+ 'Access-Control-Allow-Headers': 'Authorization, Content-Type, Accept',
+ });
+ res.status(204).send(); // Respond with no content
+ });
+
+ // Add CORS headers to all responses
+ api.use((req, res, next) => {
+ res.set('Access-Control-Allow-Origin', 'https://kronako-wiki.web.app');
+ next();
+ });
+
+ // Authorization header validation
+ api.use((req, res, next) => {
+ if (req.headers.authorization) {
+ next(); // Authorization logic passes
+ } else {
+ res.status(401).send("Authorization header is required");
+ }
+ });
+
+ // Attach middleware
api.use(this.authenticatorMiddleware.authenticate.bind(this.authenticatorMiddleware));
+
+ // Bind endpoints
const read = this.bindAndSerialize(this.read);
const write = this.bindAndSerialize(this.write);
api.get('/:wiki/recipes/:recipe/tiddlers/:title?', read);
api.get('/:wiki/bags/:bag/tiddlers/:title?', read);
- api.put('/:wiki/recipes/:recipe/tiddlers/:title', write); // create
- api.put('/:wiki/recipes/:recipe/tiddlers/:title/revisions/:revision', write); // update
- api.put('/:wiki/bags/:bag/tiddlers/:title', write); // create
- api.put('/:wiki/bags/:bag/tiddlers/:title/revisions/:revision', write); // update
+ api.put('/:wiki/recipes/:recipe/tiddlers/:title', write);
+ api.put('/:wiki/recipes/:recipe/tiddlers/:title/revisions/:revision', write);
+ api.put('/:wiki/bags/:bag/tiddlers/:title', write);
+ api.put('/:wiki/bags/:bag/tiddlers/:title/revisions/:revision', write);
api.delete('/:wiki/bags/:bag/tiddlers/:title/revisions/:revision', this.bindAndSerialize(this.remove));
+
+ // Error handling middleware
+ api.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
+ console.error(err.stack);
+ res.status(err.status || 500).send(err.message || 'Internal Server Error');
+ });
+
return api;
}
}
diff --git a/src/backend/api/policy-checker.ts b/src/backend/api/policy-checker.ts
index 1d6121b..6d09ada 100644
--- a/src/backend/api/policy-checker.ts
+++ b/src/backend/api/policy-checker.ts
@@ -105,7 +105,7 @@ export class PolicyChecker {
return {
...bagPermission,
allowed,
- reason: allowed ? undefined : PolicyRejectReason.CONTRAINTS,
+ reason: allowed ? undefined : PolicyRejectReason.CONSTRAINTS,
};
}
diff --git a/src/backend/index.ts b/src/backend/index.ts
index c695660..1bf9b61 100644
--- a/src/backend/index.ts
+++ b/src/backend/index.ts
@@ -2,16 +2,49 @@
import 'reflect-metadata';
import 'source-map-support/register';
import * as functions from 'firebase-functions';
+import cors from 'cors';
+import express, { RequestHandler } from 'express';
import { productionStartup } from './common/startup';
import { Component } from './common/ioc/components';
import { Config } from '../shared/model/config';
import { APIEndpointFactory } from './api/endpoints';
+// Initialize the IoC container and components
const container = productionStartup();
const apiEndpointFactory = container.get<APIEndpointFactory>(Component.APIEndpointFactory);
+const config = container.get<Config>(Component.config);
+// Create an Express app and apply middleware
+const app = express();
+
+// Define CORS options
+const corsOptions = {
+ origin: 'https://kronako-wiki.web.app', // Replace with your allowed origin
+ methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
+ allowedHeaders: 'Authorization, Content-Type, X-Requested-With, Accept',
+ optionsSuccessStatus: 204,
+};
+
+
+// Apply CORS middleware with explicit type casting
+const corsMiddleware = cors(corsOptions) as RequestHandler;
+app.use(corsMiddleware);
+app.options('*', corsMiddleware);
+
+// Attach the API endpoints to the Express app
+app.use('/api', apiEndpointFactory.createAPI());
+
+// Add a logging middleware for debugging
+app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
+ console.log(`[${req.method}] ${req.url}`);
+ console.log(`Headers:`, req.headers);
+ next();
+});
+
+// Export the Firebase Function
export const wiki = functions
- .region(container.get<Config>(Component.config).deploy.apiRegion)
- .runWith({
- timeoutSeconds: 540
- }).https.onRequest(apiEndpointFactory.createAPI());
+ .region(config.deploy.apiRegion)
+ .runWith({
+ timeoutSeconds: 540,
+ })
+ .https.onRequest(app);
diff --git a/src/shared/model/bag-policy.ts b/src/shared/model/bag-policy.ts
index c4fe154..fdd610e 100644
--- a/src/shared/model/bag-policy.ts
+++ b/src/shared/model/bag-policy.ts
@@ -8,7 +8,7 @@ export type BagPolicy = { [key in AccessType]: Grantee[] } & { constraints?: str
export enum PolicyRejectReason {
INSUFFICIENT_PERMISSION = 'insufficient_permissions',
- CONTRAINTS = 'constraints',
+ CONSTRAINTS = 'constraints',
}
export const standardPolicies: { [bag: string]: BagPolicy } = {
diff --git a/tsconfig.json b/tsconfig.json
index 4065bab..11aec7d 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -23,6 +23,7 @@
"typeRoots": ["node_modules/@types"],
"noUnusedLocals": true,
"noImplicitReturns": true,
+ "allowSyntheticDefaultImports": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src", "generated"],
and had to update the config.production.json
to "apiEndpoint": "https://us-central1-kronako-wiki.cloudfunctions.net/wiki/api/"