@open-xamu-co/firebase-nuxt 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/README.md +0 -14
- package/dist/module.d.mts +50 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +137 -0
- package/dist/runtime/client/types/entities/base.d.ts +29 -0
- package/dist/runtime/client/types/entities/base.js +0 -0
- package/dist/runtime/client/types/entities/instance.d.ts +42 -0
- package/dist/runtime/client/types/entities/instance.js +0 -0
- package/dist/runtime/client/types/entities/logs.d.ts +9 -0
- package/dist/runtime/client/types/entities/logs.js +0 -0
- package/dist/runtime/client/types/entities/user.d.ts +27 -0
- package/dist/runtime/client/types/entities/user.js +0 -0
- package/dist/runtime/client/types/firestore.d.ts +55 -0
- package/dist/runtime/client/types/firestore.js +0 -0
- package/dist/runtime/client/types/index.d.ts +5 -0
- package/dist/runtime/client/types/index.js +5 -0
- package/dist/runtime/client/utils/locale.d.ts +185 -0
- package/dist/runtime/client/utils/locale.js +63 -0
- package/dist/runtime/client/utils/logger.d.ts +14 -0
- package/dist/runtime/client/utils/logger.js +65 -0
- package/dist/runtime/client/utils/resolver.d.ts +14 -0
- package/dist/runtime/client/utils/resolver.js +88 -0
- package/dist/runtime/components/ValueCellphone.d.vue.ts +24 -0
- package/dist/runtime/components/ValueCellphone.vue +21 -0
- package/dist/runtime/components/ValueCellphone.vue.d.ts +24 -0
- package/dist/runtime/components/ValueID.d.vue.ts +18 -0
- package/dist/runtime/components/ValueID.vue +14 -0
- package/dist/runtime/components/ValueID.vue.d.ts +18 -0
- package/dist/runtime/components/ValueIP.d.vue.ts +18 -0
- package/dist/runtime/components/ValueIP.vue +28 -0
- package/dist/runtime/components/ValueIP.vue.d.ts +18 -0
- package/dist/runtime/components/ValueLocation.d.vue.ts +21 -0
- package/dist/runtime/components/ValueLocation.vue +20 -0
- package/dist/runtime/components/ValueLocation.vue.d.ts +21 -0
- package/dist/runtime/components/ValuePrice.d.vue.ts +11 -0
- package/dist/runtime/components/ValuePrice.vue +12 -0
- package/dist/runtime/components/ValuePrice.vue.d.ts +11 -0
- package/dist/runtime/composables/firestore/auth.d.ts +4 -0
- package/dist/runtime/composables/firestore/auth.js +46 -0
- package/dist/runtime/composables/firestore/index.d.ts +2 -0
- package/dist/runtime/composables/firestore/index.js +2 -0
- package/dist/runtime/composables/firestore/write.d.ts +21 -0
- package/dist/runtime/composables/firestore/write.js +279 -0
- package/dist/runtime/composables/index.d.ts +4 -0
- package/dist/runtime/composables/index.js +4 -0
- package/dist/runtime/composables/store/app.d.ts +82 -0
- package/dist/runtime/composables/store/app.js +90 -0
- package/dist/runtime/composables/store/instance.d.ts +57 -0
- package/dist/runtime/composables/store/instance.js +94 -0
- package/dist/runtime/composables/store/session.d.ts +41 -0
- package/dist/runtime/composables/store/session.js +89 -0
- package/dist/runtime/composables/useAppLogger.d.ts +2 -0
- package/dist/runtime/composables/useAppLogger.js +8 -0
- package/dist/runtime/composables/useFilesUpload.d.ts +19 -0
- package/dist/runtime/composables/useFilesUpload.js +110 -0
- package/dist/runtime/composables/usePrice.d.ts +9 -0
- package/dist/runtime/composables/usePrice.js +6 -0
- package/dist/runtime/composables/useQuery.d.ts +12 -0
- package/dist/runtime/composables/useQuery.js +33 -0
- package/dist/runtime/composables/utils.d.ts +14 -0
- package/dist/runtime/composables/utils.js +65 -0
- package/dist/runtime/functions/types/entities/base.d.ts +56 -0
- package/dist/runtime/functions/types/entities/base.js +0 -0
- package/dist/runtime/functions/types/entities/instance.d.ts +73 -0
- package/dist/runtime/functions/types/entities/instance.js +0 -0
- package/dist/runtime/functions/types/entities/logs.d.ts +15 -0
- package/dist/runtime/functions/types/entities/logs.js +0 -0
- package/dist/runtime/functions/types/entities/user.d.ts +13 -0
- package/dist/runtime/functions/types/entities/user.js +0 -0
- package/dist/runtime/functions/types/index.d.ts +4 -0
- package/dist/runtime/functions/types/index.js +4 -0
- package/dist/runtime/functions/utils/encrypt.d.ts +24 -0
- package/dist/runtime/functions/utils/encrypt.js +17 -0
- package/dist/runtime/functions/utils/enums.d.ts +40 -0
- package/dist/runtime/functions/utils/enums.js +24 -0
- package/dist/runtime/functions/utils/event.d.ts +71 -0
- package/dist/runtime/functions/utils/event.js +91 -0
- package/dist/runtime/functions/utils/firebase.d.ts +7 -0
- package/dist/runtime/functions/utils/firebase.js +21 -0
- package/dist/runtime/functions/utils/logger.d.ts +3 -0
- package/dist/runtime/functions/utils/logger.js +17 -0
- package/dist/runtime/functions/utils/logs.d.ts +10 -0
- package/dist/runtime/functions/utils/logs.js +47 -0
- package/dist/runtime/functions/utils/price.d.ts +12 -0
- package/dist/runtime/functions/utils/price.js +26 -0
- package/dist/runtime/functions/utils/queue.d.ts +4 -0
- package/dist/runtime/functions/utils/queue.js +11 -0
- package/dist/runtime/functions/utils/search.d.ts +15 -0
- package/dist/runtime/functions/utils/search.js +38 -0
- package/dist/runtime/functions/utils/slugs.d.ts +2 -0
- package/dist/runtime/functions/utils/slugs.js +16 -0
- package/dist/runtime/plugins/firebase-setup.d.ts +26 -0
- package/dist/runtime/plugins/firebase-setup.js +35 -0
- package/dist/runtime/plugins/loaded.client.d.ts +2 -0
- package/dist/runtime/plugins/loaded.client.js +44 -0
- package/dist/runtime/plugins/scrollBehavior.client.d.ts +2 -0
- package/dist/runtime/plugins/scrollBehavior.client.js +20 -0
- package/dist/runtime/providers/firebase.d.ts +2 -0
- package/dist/runtime/providers/firebase.js +7 -0
- package/dist/runtime/public/js/file-upload.d.ts +1 -0
- package/dist/runtime/public/js/file-upload.js +67 -0
- package/dist/runtime/public/sample-loading.png +0 -0
- package/dist/runtime/public/sample-missing.png +0 -0
- package/dist/runtime/public/sample.png +0 -0
- package/dist/runtime/server/api/all-collection-document.get.d.ts +7 -0
- package/dist/runtime/server/api/all-collection-document.get.js +56 -0
- package/dist/runtime/server/api/all-collection.get.d.ts +8 -0
- package/dist/runtime/server/api/all-collection.get.js +67 -0
- package/dist/runtime/server/api/media.get.d.ts +8 -0
- package/dist/runtime/server/api/media.get.js +114 -0
- package/dist/runtime/server/middleware/0.hotlinking.d.ts +5 -0
- package/dist/runtime/server/middleware/0.hotlinking.js +28 -0
- package/dist/runtime/server/middleware/1.context.d.ts +5 -0
- package/dist/runtime/server/middleware/1.context.js +108 -0
- package/dist/runtime/server/types/index.d.ts +34 -0
- package/dist/runtime/server/types/index.js +0 -0
- package/dist/runtime/server/utils/auth.d.ts +10 -0
- package/dist/runtime/server/utils/auth.js +31 -0
- package/dist/runtime/server/utils/cache.d.ts +21 -0
- package/dist/runtime/server/utils/cache.js +20 -0
- package/dist/runtime/server/utils/environment.d.ts +45 -0
- package/dist/runtime/server/utils/environment.js +50 -0
- package/dist/runtime/server/utils/firebase.d.ts +9 -0
- package/dist/runtime/server/utils/firebase.js +13 -0
- package/dist/runtime/server/utils/firestore.d.ts +27 -0
- package/dist/runtime/server/utils/firestore.js +137 -0
- package/dist/runtime/server/utils/guards.d.ts +15 -0
- package/dist/runtime/server/utils/guards.js +23 -0
- package/dist/runtime/server/utils/instance.d.ts +21 -0
- package/dist/runtime/server/utils/instance.js +68 -0
- package/dist/types.d.mts +7 -0
- package/package.json +17 -17
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createError, getRouterParam, isError, setResponseStatus } from "h3";
|
|
2
|
+
import { defineConditionallyCachedEventHandler } from "../utils/cache.js";
|
|
3
|
+
import { debugFirebaseServer, resolveServerDocumentRefs } from "../utils/firestore.js";
|
|
4
|
+
import { apiLogger, getServerFirebase } from "../utils/firebase.js";
|
|
5
|
+
import { readCollection, readInstanceCollection } from "#internal/firebase-nuxt";
|
|
6
|
+
export default defineConditionallyCachedEventHandler(
|
|
7
|
+
async (event) => {
|
|
8
|
+
const { firebaseFirestore } = getServerFirebase("api:all:[collectionId]:[documentId]");
|
|
9
|
+
const { currentInstanceRef } = event.context;
|
|
10
|
+
try {
|
|
11
|
+
const collectionId = getRouterParam(event, "collectionId");
|
|
12
|
+
const documentId = getRouterParam(event, "documentId");
|
|
13
|
+
debugFirebaseServer(
|
|
14
|
+
event,
|
|
15
|
+
"api:all:[collectionId]:[documentId]",
|
|
16
|
+
collectionId,
|
|
17
|
+
documentId
|
|
18
|
+
);
|
|
19
|
+
if (!collectionId || !documentId) {
|
|
20
|
+
throw createError({
|
|
21
|
+
statusCode: 400,
|
|
22
|
+
statusMessage: `collectionId & documentId are required`
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
let collectionRef = firebaseFirestore.collection(collectionId);
|
|
26
|
+
if (event.path.startsWith("/api/instance/all")) {
|
|
27
|
+
if (!readInstanceCollection(collectionId, event.context)) {
|
|
28
|
+
throw createError({
|
|
29
|
+
statusCode: 401,
|
|
30
|
+
statusMessage: `Can't get "instance/${collectionId}" document`
|
|
31
|
+
});
|
|
32
|
+
} else if (!currentInstanceRef) {
|
|
33
|
+
throw createError({ statusCode: 401, statusMessage: "Missing instance" });
|
|
34
|
+
}
|
|
35
|
+
collectionRef = currentInstanceRef.collection(collectionId);
|
|
36
|
+
} else if (!readCollection(collectionId, event.context)) {
|
|
37
|
+
throw createError({
|
|
38
|
+
statusCode: 401,
|
|
39
|
+
statusMessage: `Can't get "${collectionId}" document`
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
const documentsRef = collectionRef.doc(documentId);
|
|
43
|
+
const documentSnapshot = await documentsRef.get();
|
|
44
|
+
return resolveServerDocumentRefs(event, documentSnapshot, collectionId);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
if (isError(err)) {
|
|
47
|
+
apiLogger(event, "api:all:[collectionId]:[documentId]", err.message, err);
|
|
48
|
+
return setResponseStatus(event, err.statusCode || 500, err.statusMessage);
|
|
49
|
+
}
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
instanceOnly: false
|
|
55
|
+
}
|
|
56
|
+
);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the edges from a given instance sub-collection
|
|
3
|
+
*
|
|
4
|
+
* @auth guest
|
|
5
|
+
* @order createdAt
|
|
6
|
+
*/
|
|
7
|
+
declare const _default: import("../types/index.js").CachedEventHandler<import("h3").EventHandlerRequest, Promise<void | import("@open-xamu-co/ui-common-types").iPageEdge<FirebaseFirestore.DocumentData, string>[] | import("@open-xamu-co/ui-common-types").iPage<FirebaseFirestore.DocumentData, string>>>;
|
|
8
|
+
export default _default;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { FieldPath } from "firebase-admin/firestore";
|
|
2
|
+
import { createError, getQuery, getRouterParam, isError, setResponseStatus } from "h3";
|
|
3
|
+
import { defineConditionallyCachedEventHandler } from "../utils/cache.js";
|
|
4
|
+
import { getBoolean } from "../utils/guards.js";
|
|
5
|
+
import { getDocumentId } from "../../client/utils/resolver.js";
|
|
6
|
+
import {
|
|
7
|
+
debugFirebaseServer,
|
|
8
|
+
getEdgesPage,
|
|
9
|
+
getOrderedQuery,
|
|
10
|
+
getQueryAsEdges
|
|
11
|
+
} from "../utils/firestore.js";
|
|
12
|
+
import { apiLogger, getServerFirebase } from "../utils/firebase.js";
|
|
13
|
+
import { readCollection, readInstanceCollection } from "#internal/firebase-nuxt";
|
|
14
|
+
export default defineConditionallyCachedEventHandler(
|
|
15
|
+
async (event) => {
|
|
16
|
+
const fieldDocument = FieldPath.documentId();
|
|
17
|
+
const { firebaseFirestore } = getServerFirebase("api:all:[collectionId]");
|
|
18
|
+
const { currentInstanceRef } = event.context;
|
|
19
|
+
try {
|
|
20
|
+
const params = getQuery(event);
|
|
21
|
+
const page = getBoolean(params.page);
|
|
22
|
+
const collectionId = getRouterParam(event, "collectionId");
|
|
23
|
+
debugFirebaseServer(event, "api:all:[collectionId]", collectionId);
|
|
24
|
+
if (!collectionId) {
|
|
25
|
+
throw createError({ statusCode: 400, statusMessage: `collectionId is required` });
|
|
26
|
+
}
|
|
27
|
+
let query = firebaseFirestore.collection(collectionId);
|
|
28
|
+
if (event.path.startsWith("/api/instance/all")) {
|
|
29
|
+
if (!readInstanceCollection(collectionId, event.context)) {
|
|
30
|
+
throw createError({
|
|
31
|
+
statusCode: 401,
|
|
32
|
+
statusMessage: `Can't list "instance/${collectionId}"`
|
|
33
|
+
});
|
|
34
|
+
} else if (!currentInstanceRef) {
|
|
35
|
+
throw createError({ statusCode: 401, statusMessage: "Missing instance" });
|
|
36
|
+
}
|
|
37
|
+
query = currentInstanceRef.collection(collectionId);
|
|
38
|
+
} else if (!readCollection(collectionId, event.context)) {
|
|
39
|
+
throw createError({
|
|
40
|
+
statusCode: 401,
|
|
41
|
+
statusMessage: `Can't list "${collectionId}"`
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
if (params.include) {
|
|
45
|
+
let include = Array.isArray(params.include) ? params.include : [params.include];
|
|
46
|
+
include = include.filter((uid) => uid && !getBoolean(uid)).map(getDocumentId);
|
|
47
|
+
debugFirebaseServer(event, "api:all:[collectionId]:filtered", include);
|
|
48
|
+
if (!include.length) return [];
|
|
49
|
+
query = query.orderBy(fieldDocument).where(fieldDocument, "in", include);
|
|
50
|
+
return getQueryAsEdges(event, query);
|
|
51
|
+
}
|
|
52
|
+
query = getOrderedQuery(event, query);
|
|
53
|
+
if (page) return getEdgesPage(event, query);
|
|
54
|
+
const first = Math.min(Number(params.first) || 10, 100);
|
|
55
|
+
return getQueryAsEdges(event, query.limit(first));
|
|
56
|
+
} catch (err) {
|
|
57
|
+
if (isError(err)) {
|
|
58
|
+
apiLogger(event, "api:all:[collectionId]", err.message, err);
|
|
59
|
+
return setResponseStatus(event, err.statusCode || 500, err.statusMessage);
|
|
60
|
+
}
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
instanceOnly: false
|
|
66
|
+
}
|
|
67
|
+
);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media endpoint
|
|
3
|
+
*
|
|
4
|
+
* Buffer check because of nitro issue:
|
|
5
|
+
* @see https://github.com/unjs/nitro/issues/1894
|
|
6
|
+
*/
|
|
7
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void | Buffer<ArrayBuffer>>>;
|
|
8
|
+
export default _default;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { getStorage } from "firebase-admin/storage";
|
|
3
|
+
import { defineCachedFunction, useStorage } from "nitropack/runtime";
|
|
4
|
+
import {
|
|
5
|
+
createError,
|
|
6
|
+
defineEventHandler,
|
|
7
|
+
getRouterParam,
|
|
8
|
+
isError,
|
|
9
|
+
sendError,
|
|
10
|
+
setHeaders
|
|
11
|
+
} from "h3";
|
|
12
|
+
import { storageBucket } from "../utils/environment.js";
|
|
13
|
+
import { debugFirebaseServer } from "../utils/firestore.js";
|
|
14
|
+
import { apiLogger } from "../utils/firebase.js";
|
|
15
|
+
const maxAge = 60 * 60 * 24 * 30;
|
|
16
|
+
const preventCache = {
|
|
17
|
+
"Cache-Control": "no-cache, no-store, must-revalidate",
|
|
18
|
+
Pragma: "no-cache",
|
|
19
|
+
Expires: "0"
|
|
20
|
+
};
|
|
21
|
+
import { readCollection, readInstanceCollection } from "#internal/firebase-nuxt";
|
|
22
|
+
const cachedBufferHandler = defineCachedFunction(
|
|
23
|
+
async (event, path) => {
|
|
24
|
+
const { currentInstanceRef } = event.context;
|
|
25
|
+
debugFirebaseServer(event, "api:media", path);
|
|
26
|
+
if (!path) {
|
|
27
|
+
return { error: createError({ statusCode: 400, statusMessage: "Invalid file path" }) };
|
|
28
|
+
}
|
|
29
|
+
const [baseAndExtension] = path.split("?");
|
|
30
|
+
const [, collectionId, , fileOrSubCollectionId] = baseAndExtension.split("/");
|
|
31
|
+
if (collectionId === "instances" && !fileOrSubCollectionId.includes(".")) {
|
|
32
|
+
if (!readInstanceCollection(fileOrSubCollectionId, event.context)) {
|
|
33
|
+
throw createError({
|
|
34
|
+
statusCode: 401,
|
|
35
|
+
statusMessage: `Can't get "instance/${fileOrSubCollectionId}" file`
|
|
36
|
+
});
|
|
37
|
+
} else if (!currentInstanceRef) {
|
|
38
|
+
throw createError({ statusCode: 401, statusMessage: "Missing instance" });
|
|
39
|
+
}
|
|
40
|
+
} else if (!readCollection(collectionId, event.context)) {
|
|
41
|
+
throw createError({
|
|
42
|
+
statusCode: 401,
|
|
43
|
+
statusMessage: `Can't get "${collectionId}" file`
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const serverStorage = getStorage();
|
|
47
|
+
const bucket = serverStorage.bucket(storageBucket);
|
|
48
|
+
const file = bucket.file(baseAndExtension);
|
|
49
|
+
const [, extension] = baseAndExtension.split(".");
|
|
50
|
+
switch (extension) {
|
|
51
|
+
case "webp": {
|
|
52
|
+
const [exists] = await file.exists();
|
|
53
|
+
const headers = { "Content-Type": "image/webp" };
|
|
54
|
+
if (exists) {
|
|
55
|
+
const [buffer] = await file.download();
|
|
56
|
+
return { buffer, headers };
|
|
57
|
+
}
|
|
58
|
+
const parts = baseAndExtension.split("/");
|
|
59
|
+
parts.pop();
|
|
60
|
+
const [files] = await bucket.getFiles({ prefix: parts.join("/") });
|
|
61
|
+
if (files.length) {
|
|
62
|
+
return {
|
|
63
|
+
headers: { ...preventCache, "Retry-After": "120" },
|
|
64
|
+
error: createError({
|
|
65
|
+
statusCode: 503,
|
|
66
|
+
statusMessage: `File with path: "${path}" is not ready yet`
|
|
67
|
+
})
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
headers: preventCache,
|
|
75
|
+
error: createError({
|
|
76
|
+
statusCode: 404,
|
|
77
|
+
statusMessage: `File with path: "${path}" does not exist`
|
|
78
|
+
})
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "getMedia",
|
|
83
|
+
maxAge,
|
|
84
|
+
getKey(event, path) {
|
|
85
|
+
const [baseAndExtension] = path.split("?");
|
|
86
|
+
return createHash("sha256").update(baseAndExtension).digest("hex");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
export default defineEventHandler(async (event) => {
|
|
91
|
+
const path = getRouterParam(event, "path") || "";
|
|
92
|
+
try {
|
|
93
|
+
const { buffer, headers, error } = await cachedBufferHandler(event, path);
|
|
94
|
+
if (headers) setHeaders(event, headers);
|
|
95
|
+
if (error || !buffer) {
|
|
96
|
+
const err = error || createError({
|
|
97
|
+
statusCode: 500,
|
|
98
|
+
statusMessage: `Something went wrong while trying to get file with path: "${path}"`
|
|
99
|
+
});
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
return Buffer.from(buffer);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
const storage = useStorage("cache");
|
|
105
|
+
const [baseAndExtension] = path.split("?");
|
|
106
|
+
const hash = createHash("sha256").update(baseAndExtension).digest("hex");
|
|
107
|
+
await storage.removeItem(`nitro:functions:getMedia:${hash}.json`);
|
|
108
|
+
if (isError(err)) {
|
|
109
|
+
if (err.statusCode !== 503) apiLogger(event, "api:media:[...path]", err.message, err);
|
|
110
|
+
return sendError(event, err);
|
|
111
|
+
}
|
|
112
|
+
throw err;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { isError, defineEventHandler, setResponseHeaders, setResponseStatus } from "h3";
|
|
2
|
+
import { apiLogger } from "../utils/firebase.js";
|
|
3
|
+
import { getRootInstance } from "../utils/instance.js";
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
try {
|
|
6
|
+
const root = await getRootInstance(event);
|
|
7
|
+
if (!root?.url) return setResponseStatus(event, 500, "Missing root instance");
|
|
8
|
+
const { hostname } = new URL(root.url);
|
|
9
|
+
setResponseHeaders(event, {
|
|
10
|
+
// Prevent clickjacking
|
|
11
|
+
"X-Frame-Options": "SAMEORIGIN",
|
|
12
|
+
// Legacy XSS protection
|
|
13
|
+
"X-XSS-Protection": "1; mode=block",
|
|
14
|
+
// Prevent MIME type sniffing
|
|
15
|
+
"X-Content-Type-Options": "nosniff",
|
|
16
|
+
// Prevent referrer leaks
|
|
17
|
+
"Referrer-Policy": "strict-origin-when-cross-origin",
|
|
18
|
+
// CSP Defaults, safe assets only
|
|
19
|
+
"Content-Security-Policy": `default-src 'self'; frame-src 'self' data: https:;img-src 'self' data: https:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; style-src 'self' 'unsafe-inline' https:; font-src 'self' data: https:; connect-src 'self' https:; frame-ancestors 'self' https://*.${hostname};`
|
|
20
|
+
});
|
|
21
|
+
} catch (err) {
|
|
22
|
+
if (isError(err)) {
|
|
23
|
+
apiLogger(event, "api:middleware:hotlinking", err.message, err);
|
|
24
|
+
return setResponseStatus(event, err.statusCode, err.message);
|
|
25
|
+
}
|
|
26
|
+
throw err;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { FirebaseAuthError } from "firebase-admin/auth";
|
|
3
|
+
import {
|
|
4
|
+
isError,
|
|
5
|
+
getRequestHeaders,
|
|
6
|
+
defineEventHandler,
|
|
7
|
+
setResponseHeaders,
|
|
8
|
+
setResponseStatus,
|
|
9
|
+
setResponseHeader,
|
|
10
|
+
getRequestHeader
|
|
11
|
+
} from "h3";
|
|
12
|
+
import { useStorage } from "nitropack/runtime";
|
|
13
|
+
import { production } from "../utils/environment.js";
|
|
14
|
+
import { apiLogger, getServerFirebase } from "../utils/firebase.js";
|
|
15
|
+
import { getInstance } from "../utils/instance.js";
|
|
16
|
+
import { getAuth } from "../utils/auth.js";
|
|
17
|
+
export default defineEventHandler(async (event) => {
|
|
18
|
+
const { firebaseFirestore } = getServerFirebase("api:middleware:context");
|
|
19
|
+
const headers = getRequestHeaders(event);
|
|
20
|
+
const corsHeaders = ["Origin", "Referer", "User-Agent"].join(", ");
|
|
21
|
+
if (!event.path.startsWith("/api")) {
|
|
22
|
+
setResponseHeaders(event, {
|
|
23
|
+
"Access-Control-Allow-Methods": "GET,HEAD",
|
|
24
|
+
Vary: "Host, Origin"
|
|
25
|
+
});
|
|
26
|
+
if (event.method === "OPTIONS") {
|
|
27
|
+
setResponseHeaders(event, {
|
|
28
|
+
"Access-Control-Allow-Headers": corsHeaders,
|
|
29
|
+
"Access-Control-Expose-Headers": corsHeaders
|
|
30
|
+
});
|
|
31
|
+
setResponseStatus(event, 204, "No Content");
|
|
32
|
+
}
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const {
|
|
36
|
+
host = "",
|
|
37
|
+
"x-forwarded-host": forwardedHost = host,
|
|
38
|
+
"xamu-context-hits": contextHits = "0"
|
|
39
|
+
} = headers;
|
|
40
|
+
const [cleanHost] = forwardedHost.split(":");
|
|
41
|
+
try {
|
|
42
|
+
setResponseHeader(event, "Xamu-Context-Hits", String(Number(contextHits) + 1));
|
|
43
|
+
const { millis, ...instance } = await getInstance(event, forwardedHost);
|
|
44
|
+
const instanceRef = firebaseFirestore.doc(instance.id);
|
|
45
|
+
event.context.currentInstance = instance;
|
|
46
|
+
event.context.currentInstanceRef = instanceRef;
|
|
47
|
+
event.context.currentInstanceMillis = millis;
|
|
48
|
+
event.context.currentInstanceHost = cleanHost;
|
|
49
|
+
const origin = getRequestHeader(event, "X-Forwarded-Origin") || getRequestHeader(event, "Origin") || `https://${cleanHost}`;
|
|
50
|
+
const { hostname } = new URL(origin);
|
|
51
|
+
let corsOrigin = production ? `https://${cleanHost}` : "*";
|
|
52
|
+
if (instance.config?.domains?.includes(hostname)) corsOrigin = origin;
|
|
53
|
+
const corsHeadersExpose = [corsHeaders, "Content-Type", "Cache-control"].join(", ");
|
|
54
|
+
const corsHeadersAccept = [
|
|
55
|
+
corsHeadersExpose,
|
|
56
|
+
"Authorization",
|
|
57
|
+
"Host",
|
|
58
|
+
"Xamu-Context-Source"
|
|
59
|
+
].join(", ");
|
|
60
|
+
setResponseHeaders(event, {
|
|
61
|
+
"Access-Control-Allow-Methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
|
|
62
|
+
"Access-Control-Allow-Credentials": "true",
|
|
63
|
+
"Access-Control-Allow-Origin": corsOrigin,
|
|
64
|
+
Vary: "Host, Origin"
|
|
65
|
+
});
|
|
66
|
+
if (event.method === "OPTIONS") {
|
|
67
|
+
setResponseHeaders(event, {
|
|
68
|
+
"Access-Control-Allow-Headers": corsHeadersAccept,
|
|
69
|
+
"Access-Control-Expose-Headers": corsHeadersExpose
|
|
70
|
+
});
|
|
71
|
+
setResponseStatus(event, 204, "No Content");
|
|
72
|
+
}
|
|
73
|
+
const authorization = getRequestHeader(event, "X-Forwarded-Authorization") || getRequestHeader(event, "Authorization");
|
|
74
|
+
if (!authorization) return;
|
|
75
|
+
try {
|
|
76
|
+
const auth = await getAuth(event, authorization);
|
|
77
|
+
if (!auth) return;
|
|
78
|
+
event.context.currentAuth = auth;
|
|
79
|
+
event.context.currentAuthRef = instanceRef.collection("members").doc(auth.uid);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
const storage = useStorage("cache");
|
|
82
|
+
const hash = createHash("sha256").update(authorization).digest("hex");
|
|
83
|
+
await storage.removeItem(`nitro:functions:getAuth:${hash}.json`);
|
|
84
|
+
if (err instanceof FirebaseAuthError || isError(err)) {
|
|
85
|
+
if ("code" in err && err.code === "auth/id-token-expired") return;
|
|
86
|
+
apiLogger(
|
|
87
|
+
event,
|
|
88
|
+
"api:middleware:context:auth",
|
|
89
|
+
`Path: "${event.path}", ${err.message}`,
|
|
90
|
+
err
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
} catch (err) {
|
|
95
|
+
const storage = useStorage("cache");
|
|
96
|
+
await storage.removeItem(`nitro:functions:getInstance:${cleanHost}.json`);
|
|
97
|
+
if (isError(err)) {
|
|
98
|
+
apiLogger(
|
|
99
|
+
event,
|
|
100
|
+
"api:middleware:context",
|
|
101
|
+
`Path: "${event.path}", ${err.message}`,
|
|
102
|
+
err
|
|
103
|
+
);
|
|
104
|
+
return setResponseStatus(event, err.statusCode, err.message);
|
|
105
|
+
}
|
|
106
|
+
throw err;
|
|
107
|
+
}
|
|
108
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { DocumentReference } from "firebase-admin/firestore";
|
|
2
|
+
import type { EventHandler, EventHandlerRequest, EventHandlerResponse, H3Event, H3EventContext } from "h3";
|
|
3
|
+
import type { InstanceData } from "../../functions/types/index.js";
|
|
4
|
+
import type { Instance, User } from "../../client/types/index.js";
|
|
5
|
+
export interface H3Context extends H3EventContext {
|
|
6
|
+
currentInstance?: Instance & {
|
|
7
|
+
millis: string;
|
|
8
|
+
url: string;
|
|
9
|
+
id: string;
|
|
10
|
+
};
|
|
11
|
+
currentInstanceRef?: DocumentReference<InstanceData>;
|
|
12
|
+
/**
|
|
13
|
+
* Milliseconds from creation
|
|
14
|
+
*/
|
|
15
|
+
currentInstanceMillis?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Clean host without port
|
|
18
|
+
*
|
|
19
|
+
* @example "example.com"
|
|
20
|
+
* @cache used for instance cache key
|
|
21
|
+
*/
|
|
22
|
+
currentInstanceHost?: string;
|
|
23
|
+
currentAuth?: User & {
|
|
24
|
+
id: string;
|
|
25
|
+
uid: string;
|
|
26
|
+
};
|
|
27
|
+
currentAuthRef?: DocumentReference;
|
|
28
|
+
}
|
|
29
|
+
export interface CachedH3Event<T extends EventHandlerRequest = EventHandlerRequest> extends Omit<H3Event<T>, "context"> {
|
|
30
|
+
context: H3Context;
|
|
31
|
+
}
|
|
32
|
+
export interface CachedEventHandler<T extends EventHandlerRequest = EventHandlerRequest, D extends EventHandlerResponse = EventHandlerResponse> extends Omit<EventHandler<T, D>, "event"> {
|
|
33
|
+
(event: CachedH3Event<T>): D;
|
|
34
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type H3Event } from "h3";
|
|
2
|
+
/**
|
|
3
|
+
* Get current auth
|
|
4
|
+
*
|
|
5
|
+
* @cache 2 minutes
|
|
6
|
+
*/
|
|
7
|
+
export declare const getAuth: (_: H3Event<import("h3").EventHandlerRequest>, authorization: string | undefined) => Promise<(import("../../client/types/index.js").User & {
|
|
8
|
+
id: string;
|
|
9
|
+
uid: string;
|
|
10
|
+
}) | undefined>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { defineCachedFunction } from "nitropack/runtime";
|
|
3
|
+
import { resolveServerRefs } from "./firestore.js";
|
|
4
|
+
import { getServerFirebase } from "./firebase.js";
|
|
5
|
+
export const getAuth = defineCachedFunction(
|
|
6
|
+
async function(event, authorization) {
|
|
7
|
+
if (!authorization) return;
|
|
8
|
+
const { firebaseAuth } = getServerFirebase("api:getAuth");
|
|
9
|
+
const { currentInstanceRef, currentInstance } = event.context;
|
|
10
|
+
if (!currentInstanceRef || !currentInstance) return;
|
|
11
|
+
const membersRef = currentInstanceRef.collection("members");
|
|
12
|
+
const { uid } = await firebaseAuth.verifyIdToken(authorization);
|
|
13
|
+
const snapshot = await membersRef.doc(uid).get();
|
|
14
|
+
const memberData = await resolveServerRefs(
|
|
15
|
+
snapshot,
|
|
16
|
+
{ level: 1 },
|
|
17
|
+
false
|
|
18
|
+
);
|
|
19
|
+
const { user, ...member } = memberData || {};
|
|
20
|
+
return { ...user, ...member, uid, id: `${currentInstance.id}/members/${uid}` };
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "getAuth",
|
|
24
|
+
maxAge: 60 * 60,
|
|
25
|
+
// 1 hour
|
|
26
|
+
getKey(_, authorization) {
|
|
27
|
+
if (!authorization) return "guest";
|
|
28
|
+
return createHash("sha256").update(authorization).digest("hex");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type EventHandlerRequest, type EventHandlerResponse } from "h3";
|
|
2
|
+
import type { CachedEventHandler, CachedH3Event } from "../types/index.js";
|
|
3
|
+
interface CachedEventHandlerOptions<T extends EventHandlerRequest = EventHandlerRequest> {
|
|
4
|
+
/** Optional key generator */
|
|
5
|
+
getKey?: (...args: [CachedH3Event<T>]) => string | Promise<string>;
|
|
6
|
+
/** Partition cache by instance */
|
|
7
|
+
instanceOnly?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Conditionally cache event data.
|
|
11
|
+
* Bypasses cache for admin purposes
|
|
12
|
+
* Caches by instance by default
|
|
13
|
+
*
|
|
14
|
+
* @cache 30 seconds
|
|
15
|
+
*
|
|
16
|
+
* @param handler event handler, should have its own error handling
|
|
17
|
+
* @param options optional key generator and instanceOnly flag
|
|
18
|
+
* @returns event handler
|
|
19
|
+
*/
|
|
20
|
+
export declare const defineConditionallyCachedEventHandler: <T extends EventHandlerRequest, D extends EventHandlerResponse = EventHandlerResponse>(handler: CachedEventHandler<T, D>, { getKey, instanceOnly }?: CachedEventHandlerOptions<T>) => CachedEventHandler<T, D>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineEventHandler } from "h3";
|
|
2
|
+
import { defineCachedEventHandler } from "nitropack/runtime";
|
|
3
|
+
import { debugNitro } from "../utils/environment.js";
|
|
4
|
+
import { sudo } from "#internal/firebase-nuxt";
|
|
5
|
+
export const defineConditionallyCachedEventHandler = (handler, { getKey, instanceOnly = true } = {}) => {
|
|
6
|
+
const cachedHandler = defineCachedEventHandler(handler, {
|
|
7
|
+
maxAge: 30,
|
|
8
|
+
// 30 seconds
|
|
9
|
+
getKey: instanceOnly ? (event) => {
|
|
10
|
+
const { currentInstanceHost } = event.context;
|
|
11
|
+
const key = getKey?.(event) || event.path;
|
|
12
|
+
if (currentInstanceHost) return `${currentInstanceHost}:${key}`;
|
|
13
|
+
return key;
|
|
14
|
+
} : getKey
|
|
15
|
+
});
|
|
16
|
+
return defineEventHandler(async (event) => {
|
|
17
|
+
if (sudo(event.context) || debugNitro) return handler(event);
|
|
18
|
+
return cachedHandler(event);
|
|
19
|
+
});
|
|
20
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { FirebaseOptions } from "firebase/app";
|
|
2
|
+
import { eCacheControl } from "../../functions/utils/enums.js";
|
|
3
|
+
/**
|
|
4
|
+
* Public runtime config
|
|
5
|
+
*/
|
|
6
|
+
export interface FirebaseNuxtPublicRuntimeConfig {
|
|
7
|
+
appName: string;
|
|
8
|
+
production: boolean;
|
|
9
|
+
forcedInstanceId: string;
|
|
10
|
+
indexable: boolean;
|
|
11
|
+
tenants: boolean;
|
|
12
|
+
firebaseConfig: Required<Omit<FirebaseOptions, "databaseURL">>;
|
|
13
|
+
recaptchaEnterpriseKey: string;
|
|
14
|
+
debugAppCheck: boolean;
|
|
15
|
+
debugFirebase: boolean;
|
|
16
|
+
cache: {
|
|
17
|
+
none: eCacheControl;
|
|
18
|
+
frequent: eCacheControl;
|
|
19
|
+
normal: eCacheControl;
|
|
20
|
+
midterm: eCacheControl;
|
|
21
|
+
longterm: eCacheControl;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export declare const port: number;
|
|
25
|
+
export declare const production: boolean;
|
|
26
|
+
export declare const indexable: boolean;
|
|
27
|
+
export declare const origin: string;
|
|
28
|
+
export declare const forcedInstanceId: string;
|
|
29
|
+
export declare const appName: string;
|
|
30
|
+
export declare const debugNuxt: boolean;
|
|
31
|
+
export declare const debugNitro: boolean;
|
|
32
|
+
export declare const debugCSS: boolean;
|
|
33
|
+
export declare const debugAppCheck: boolean;
|
|
34
|
+
export declare const debugFirebase: boolean;
|
|
35
|
+
export declare const storageBucket: string;
|
|
36
|
+
export declare const projectId: string;
|
|
37
|
+
export declare const privateKey: string;
|
|
38
|
+
export declare const clientEmail: string;
|
|
39
|
+
/** App check, public key */
|
|
40
|
+
export declare const recaptchaEnterpriseKey: string;
|
|
41
|
+
export declare const fontsApiKey: string;
|
|
42
|
+
/**
|
|
43
|
+
* Public runtime config
|
|
44
|
+
*/
|
|
45
|
+
export declare const publicRuntimeConfig: FirebaseNuxtPublicRuntimeConfig;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { defineString, defineBoolean, defineInt } from "firebase-functions/params";
|
|
2
|
+
import { eCacheControl } from "../../functions/utils/enums.js";
|
|
3
|
+
const environment = defineString("NODE_ENV", { default: "development" });
|
|
4
|
+
export const port = defineInt("PORT", { default: 3e3 }).value() || 3e3;
|
|
5
|
+
export const production = environment.equals("production").value();
|
|
6
|
+
export const indexable = defineBoolean("INDEXABLE", { default: false }).value();
|
|
7
|
+
export const origin = defineString("ORIGIN").value();
|
|
8
|
+
export const forcedInstanceId = defineString("INSTANCE").value();
|
|
9
|
+
export const appName = defineString("APP_NAME").value();
|
|
10
|
+
export const debugNuxt = !production && defineBoolean("DEBUG_NUXT", { default: false }).value();
|
|
11
|
+
export const debugNitro = !production && defineBoolean("DEBUG_NITRO", { default: false }).value();
|
|
12
|
+
export const debugCSS = !production && defineBoolean("DEBUG_CSS", { default: false }).value();
|
|
13
|
+
export const debugAppCheck = !production && defineBoolean("DEBUG_APP_CHECK", { default: false }).value();
|
|
14
|
+
export const debugFirebase = !production && defineBoolean("DEBUG_FIREBASE", { default: false }).value();
|
|
15
|
+
export const storageBucket = defineString("F_STORAGE_BUCKET").value();
|
|
16
|
+
export const projectId = defineString("F_PROJECT_ID").value();
|
|
17
|
+
export const privateKey = defineString("F_PRIVATE_KEY").value();
|
|
18
|
+
export const clientEmail = defineString("F_CLIENT_EMAIL").value();
|
|
19
|
+
export const recaptchaEnterpriseKey = defineString("RECAPTCHA_ENTERPRISE_SITE_KEY").value();
|
|
20
|
+
export const fontsApiKey = defineString("FONTS_API_KEY").value();
|
|
21
|
+
const firebaseConfig = {
|
|
22
|
+
projectId,
|
|
23
|
+
apiKey: defineString("F_API_KEY").value(),
|
|
24
|
+
authDomain: defineString("F_AUTH_DOMAIN").value(),
|
|
25
|
+
storageBucket,
|
|
26
|
+
messagingSenderId: defineString("F_MESSAGING_SENDER_ID").value(),
|
|
27
|
+
appId: defineString("F_APP_ID").value(),
|
|
28
|
+
measurementId: defineString("F_MEASUREMENT_ID").value()
|
|
29
|
+
};
|
|
30
|
+
export const publicRuntimeConfig = {
|
|
31
|
+
// App
|
|
32
|
+
appName,
|
|
33
|
+
production,
|
|
34
|
+
forcedInstanceId,
|
|
35
|
+
indexable,
|
|
36
|
+
// Firebase
|
|
37
|
+
firebaseConfig,
|
|
38
|
+
recaptchaEnterpriseKey,
|
|
39
|
+
debugAppCheck,
|
|
40
|
+
debugFirebase,
|
|
41
|
+
// Utils
|
|
42
|
+
cache: {
|
|
43
|
+
none: eCacheControl.NONE,
|
|
44
|
+
frequent: eCacheControl.FREQUENT,
|
|
45
|
+
normal: eCacheControl.NORMAL,
|
|
46
|
+
midterm: eCacheControl.MIDTERM,
|
|
47
|
+
longterm: eCacheControl.LONGTERM
|
|
48
|
+
},
|
|
49
|
+
tenants: false
|
|
50
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { H3Event } from "h3";
|
|
2
|
+
import type { tLogger } from "@open-xamu-co/ui-common-types";
|
|
3
|
+
export declare function getServerFirebase(at?: string): {
|
|
4
|
+
firebaseApp: import("firebase-admin/app").App;
|
|
5
|
+
firebaseFirestore: FirebaseFirestore.Firestore;
|
|
6
|
+
firebaseAuth: import("firebase-admin/auth").Auth;
|
|
7
|
+
firebaseStorage: import("firebase-admin/storage").Storage;
|
|
8
|
+
};
|
|
9
|
+
export declare function apiLogger(event: H3Event, ...args: Parameters<tLogger>): void;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { cert } from "firebase-admin/app";
|
|
2
|
+
import { clientEmail, privateKey, projectId } from "../utils/environment.js";
|
|
3
|
+
import { makeLogger } from "../../client/utils/logger.js";
|
|
4
|
+
import { getFirebase } from "../../functions/utils/firebase.js";
|
|
5
|
+
export function getServerFirebase(at = "Unknown") {
|
|
6
|
+
const credential = cert({ projectId, privateKey, clientEmail });
|
|
7
|
+
return getFirebase(at, { credential });
|
|
8
|
+
}
|
|
9
|
+
export function apiLogger(event, ...args) {
|
|
10
|
+
const { currentAuth, currentInstance } = event.context || {};
|
|
11
|
+
const logger = makeLogger({ instanceId: currentInstance?.id, authId: currentAuth?.uid });
|
|
12
|
+
return logger(...args);
|
|
13
|
+
}
|