maxibase 1.2.1 → 1.3.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.
@@ -9,7 +9,7 @@ services:
9
9
  - ./volumes/files/private:/app/${NUXT_FILES_PRIVATE_FOLDER:-files/private}
10
10
  - ./volumes/backups:/app/backups
11
11
  ports:
12
- - ${SERVER_PORT:-3000}:3000
12
+ - ${SERVER_HOST:-0.0.0.0}:${SERVER_PORT:-3000}:3000
13
13
  networks:
14
14
  - shared-network
15
15
 
@@ -70,8 +70,8 @@ function formatDay(utcTimestamp: string) {
70
70
  }
71
71
 
72
72
  function originUrl(origin: string | null) {
73
- const url = origin?.replace(/ .*/, '')
74
- return url?.startsWith('/') || url?.startsWith('http') ? url : null
73
+ const url = origin?.replace(/^client /, '').replace(/ .*/, '')
74
+ return url && /^\/(?![/\\])/.test(url) ? url : null
75
75
  }
76
76
 
77
77
  function toggleExpanded(id: number, isOpen: boolean) {
@@ -93,7 +93,9 @@ export default defineNuxtConfig({
93
93
  },
94
94
  storage: {
95
95
  auth: {
96
- driver: 'memory',
96
+ driver: 'lru-cache',
97
+ max: 10_000,
98
+ ttl: 10 * 60 * 1000,
97
99
  },
98
100
  },
99
101
  serverAssets: [
@@ -16,5 +16,5 @@ const bodySchema = z.object({
16
16
 
17
17
  export default defineEventHandler(async (event) => {
18
18
  const { summary, data, origin, type } = await readValidatedBody(event, bodySchema.parse)
19
- await log(summary, data, origin, type, event)
19
+ await log(summary, data, origin ? `client ${origin}` : 'client', type, event)
20
20
  })
@@ -1,4 +1,5 @@
1
1
  import type { H3Event } from 'h3'
2
+ import { isIPv6 } from 'node:net'
2
3
 
3
4
  export interface RateLimitRule {
4
5
  limit: number // max requests per window
@@ -24,7 +25,31 @@ setInterval(() => {
24
25
  async function getId(event: H3Event): Promise<string> {
25
26
  const { user } = await getUserSession(event)
26
27
  if (user) return user.id
27
- return getClientIP(event)
28
+ return rateLimitIP(getClientIP(event))
29
+ }
30
+
31
+ // An IPv6 client usually holds a whole /64, so counting single addresses would let it rotate
32
+ // through 2^64 of them. Count the /64 instead, and IPv4-mapped addresses as their IPv4.
33
+ export function rateLimitIP(ip: string): string {
34
+ const address = ip.split('%')[0]!.toLowerCase() // drop the zone id
35
+ if (!isIPv6(address)) return ip
36
+
37
+ const mapped = address.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/)
38
+ if (mapped) return mapped[1]!
39
+
40
+ // an embedded IPv4 tail fills the last two groups
41
+ const toGroups = (part: string) =>
42
+ part ? part.split(':').flatMap((group) => (group.includes('.') ? ['0', '0'] : [group])) : []
43
+ const [head = '', tail] = address.split('::')
44
+ let groups = toGroups(head)
45
+ if (tail !== undefined) {
46
+ const tailGroups = toGroups(tail)
47
+ groups = [...groups, ...Array(8 - groups.length - tailGroups.length).fill('0'), ...tailGroups]
48
+ }
49
+ return `${groups
50
+ .slice(0, 4)
51
+ .map((group) => parseInt(group, 16).toString(16))
52
+ .join(':')}::/64`
28
53
  }
29
54
 
30
55
  export async function enforceRateLimit(
package/nuxt.config.ts CHANGED
@@ -13,10 +13,7 @@ export default defineNuxtConfig({
13
13
  lang: 'en',
14
14
  },
15
15
  title: process.env.NUXT_PUBLIC_NAME,
16
- link: [
17
- { rel: 'icon', href: '/favicon.ico', sizes: '32x32' },
18
- { rel: 'icon', type: 'image/svg+xml', href: '/logo.svg' },
19
- ],
16
+ link: [{ rel: 'icon', href: '/favicon.ico', sizes: '32x32' }],
20
17
  meta: [{ name: 'theme-color', content: '#000000' }],
21
18
  },
22
19
  },
@@ -29,7 +26,7 @@ export default defineNuxtConfig({
29
26
  rateLimit: {
30
27
  routes: {
31
28
  // limit: max requests per IP, window: in seconds
32
- '/api/log': { limit: 30, window: 60 },
29
+ '/api/log': { limit: 10, window: 3600 },
33
30
  '/api/auth/options': { limit: 10, window: 60 },
34
31
  '/api/auth/password': { limit: 5, window: 60 },
35
32
  '/api/auth/otp/get': { limit: 5, window: 900 },
@@ -38,6 +35,7 @@ export default defineNuxtConfig({
38
35
  '/api/auth/otp/verify': { limit: 10, window: 60 },
39
36
  '/api/auth/totp/verify': { limit: 10, window: 60 },
40
37
  '/api/auth/webauthn/authenticate': { limit: 10, window: 60 },
38
+ '/api/auth/webauthn/register': { limit: 10, window: 60 },
41
39
  '/api/auth/anonymous': { limit: 5, window: 3600 },
42
40
  },
43
41
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maxibase",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "Nuxt layer with auth, files, logs and backups",
5
5
  "keywords": [
6
6
  "nuxt",
@@ -58,12 +58,12 @@
58
58
  "prepublishOnly": "pnpm lint && pnpm fmt:check && pnpm typecheck && pnpm test"
59
59
  },
60
60
  "dependencies": {
61
- "@aws-sdk/client-s3": "^3.1146.0",
62
- "@aws-sdk/s3-request-presigner": "^3.1146.0",
63
- "@iconify-json/lucide": "^1.2.140",
64
- "@nuxt/hints": "^1.1.4",
61
+ "@aws-sdk/client-s3": "^3.1154.0",
62
+ "@aws-sdk/s3-request-presigner": "^3.1154.0",
63
+ "@iconify-json/lucide": "^1.2.147",
64
+ "@nuxt/hints": "^1.1.5",
65
65
  "@nuxt/icon": "^2.5.1",
66
- "@pinia/nuxt": "^1.0.2",
66
+ "@pinia/nuxt": "^1.0.3",
67
67
  "@simplewebauthn/browser": "^11.0.0",
68
68
  "@simplewebauthn/server": "^11.0.0",
69
69
  "@types/nodemailer": "^8.0.2",
@@ -71,24 +71,24 @@
71
71
  "@types/qrcode": "^1.5.6",
72
72
  "drizzle-orm": "1.0.0-rc.3",
73
73
  "goku-css": "^9.0.8",
74
- "nodemailer": "^9.1.1",
75
- "nuxt-auth-utils": "^0.5.30",
76
- "nuxt-i18n-micro": "^3.30.0",
74
+ "nodemailer": "^10.1.7",
75
+ "nuxt-auth-utils": "^0.5.32",
76
+ "nuxt-i18n-micro": "^3.30.1",
77
77
  "otplib": "^13.5.0",
78
78
  "pg": "^8.23.1",
79
- "pinia": "^4.0.3",
79
+ "pinia": "^4.1.0",
80
80
  "qrcode": "^1.5.4",
81
- "zod": "^4.6.5"
81
+ "zod": "^4.6.7"
82
82
  },
83
83
  "devDependencies": {
84
84
  "drizzle-kit": "1.0.0-rc.3",
85
85
  "drizzle-seed": "1.0.0-rc.3",
86
86
  "maxibase": "link:.",
87
- "nuxt": "^4.6.0",
87
+ "nuxt": "^4.6.2",
88
88
  "oxfmt": "^0.72.0",
89
- "oxlint": "^1.87.0",
89
+ "oxlint": "^1.89.0",
90
90
  "typescript": "^5.9.3",
91
- "vitest": "^5.0.3",
92
- "vue-tsc": "^3.3.12"
91
+ "vitest": "^5.0.5",
92
+ "vue-tsc": "^3.3.13"
93
93
  }
94
94
  }