mirror of
https://github.com/fosrl/pangolin.git
synced 2025-08-29 14:18:26 +02:00
feat: Add setup token security for initial server setup
- Add setupTokens database table with proper schema - Implement setup token generation on first server startup - Add token validation endpoint and modify admin creation - Update initial setup page to require setup token - Add migration scripts for both SQLite and PostgreSQL - Add internationalization support for setup token fields - Implement proper error handling and logging - Add CLI command for resetting user security keys This prevents unauthorized access during initial server setup by requiring a token that is generated and displayed in the server console.
This commit is contained in:
parent
84268e484d
commit
69baa6785f
15 changed files with 322 additions and 115 deletions
|
@ -967,6 +967,9 @@
|
||||||
"actionDeleteSite": "Delete Site",
|
"actionDeleteSite": "Delete Site",
|
||||||
"actionGetSite": "Get Site",
|
"actionGetSite": "Get Site",
|
||||||
"actionListSites": "List Sites",
|
"actionListSites": "List Sites",
|
||||||
|
"setupToken": "Setup Token",
|
||||||
|
"setupTokenPlaceholder": "Enter the setup token from the server console",
|
||||||
|
"setupTokenRequired": "Setup token is required",
|
||||||
"actionUpdateSite": "Update Site",
|
"actionUpdateSite": "Update Site",
|
||||||
"actionListSiteRoles": "List Allowed Site Roles",
|
"actionListSiteRoles": "List Allowed Site Roles",
|
||||||
"actionCreateResource": "Create Resource",
|
"actionCreateResource": "Create Resource",
|
||||||
|
|
110
package-lock.json
generated
110
package-lock.json
generated
|
@ -61,7 +61,6 @@
|
||||||
"http-errors": "2.0.0",
|
"http-errors": "2.0.0",
|
||||||
"i": "^0.3.7",
|
"i": "^0.3.7",
|
||||||
"input-otp": "1.4.2",
|
"input-otp": "1.4.2",
|
||||||
"ioredis": "^5.6.1",
|
|
||||||
"jmespath": "^0.16.0",
|
"jmespath": "^0.16.0",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
@ -77,7 +76,6 @@
|
||||||
"oslo": "1.2.1",
|
"oslo": "1.2.1",
|
||||||
"pg": "^8.16.2",
|
"pg": "^8.16.2",
|
||||||
"qrcode.react": "4.2.0",
|
"qrcode.react": "4.2.0",
|
||||||
"rate-limit-redis": "^4.2.1",
|
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"react-easy-sort": "^1.6.0",
|
"react-easy-sort": "^1.6.0",
|
||||||
|
@ -2010,12 +2008,6 @@
|
||||||
"url": "https://opencollective.com/libvips"
|
"url": "https://opencollective.com/libvips"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ioredis/commands": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
|
|
||||||
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@isaacs/balanced-match": {
|
"node_modules/@isaacs/balanced-match": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
|
||||||
|
@ -2058,6 +2050,7 @@
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
|
||||||
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
|
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minipass": "^7.0.4"
|
"minipass": "^7.0.4"
|
||||||
|
@ -6309,6 +6302,7 @@
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
|
||||||
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
|
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
|
||||||
|
"dev": true,
|
||||||
"license": "BlueOak-1.0.0",
|
"license": "BlueOak-1.0.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
|
@ -6455,15 +6449,6 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cluster-key-slot": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
|
|
||||||
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cmdk": {
|
"node_modules/cmdk": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz",
|
||||||
|
@ -6947,15 +6932,6 @@
|
||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/denque": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/depd": {
|
"node_modules/depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
|
@ -8835,6 +8811,7 @@
|
||||||
"version": "4.2.11",
|
"version": "4.2.11",
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/graphemer": {
|
"node_modules/graphemer": {
|
||||||
|
@ -9122,30 +9099,6 @@
|
||||||
"tslib": "^2.8.0"
|
"tslib": "^2.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ioredis": {
|
|
||||||
"version": "5.6.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz",
|
|
||||||
"integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@ioredis/commands": "^1.1.1",
|
|
||||||
"cluster-key-slot": "^1.1.0",
|
|
||||||
"debug": "^4.3.4",
|
|
||||||
"denque": "^2.1.0",
|
|
||||||
"lodash.defaults": "^4.2.0",
|
|
||||||
"lodash.isarguments": "^3.1.0",
|
|
||||||
"redis-errors": "^1.2.0",
|
|
||||||
"redis-parser": "^3.0.0",
|
|
||||||
"standard-as-callback": "^2.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.22.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/ioredis"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ipaddr.js": {
|
"node_modules/ipaddr.js": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
|
@ -9606,6 +9559,7 @@
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz",
|
||||||
"integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==",
|
"integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
|
@ -10112,24 +10066,12 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lodash.defaults": {
|
|
||||||
"version": "4.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
|
||||||
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/lodash.includes": {
|
"node_modules/lodash.includes": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
|
||||||
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
|
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lodash.isarguments": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/lodash.isboolean": {
|
"node_modules/lodash.isboolean": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
|
||||||
|
@ -10481,6 +10423,7 @@
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz",
|
||||||
"integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
|
"integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minipass": "^7.1.2"
|
"minipass": "^7.1.2"
|
||||||
|
@ -10493,6 +10436,7 @@
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
|
||||||
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
|
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"mkdirp": "dist/cjs/src/bin.js"
|
"mkdirp": "dist/cjs/src/bin.js"
|
||||||
|
@ -14470,18 +14414,6 @@
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/rate-limit-redis": {
|
|
||||||
"version": "4.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/rate-limit-redis/-/rate-limit-redis-4.2.1.tgz",
|
|
||||||
"integrity": "sha512-JsUsVmRVI6G/XrlYtfGV1NMCbGS/CVYayHkxD5Ism5FaL8qpFHCXbFkUeIi5WJ/onJOKWCgtB/xtCLa6qSXb4g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 16"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"express-rate-limit": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/raw-body": {
|
"node_modules/raw-body": {
|
||||||
"version": "2.5.2",
|
"version": "2.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
|
||||||
|
@ -14828,27 +14760,6 @@
|
||||||
"node": ">=0.8.8"
|
"node": ">=0.8.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/redis-errors": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
|
|
||||||
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/redis-parser": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"redis-errors": "^1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/reflect.getprototypeof": {
|
"node_modules/reflect.getprototypeof": {
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
||||||
|
@ -15628,12 +15539,6 @@
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/standard-as-callback": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
|
|
||||||
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
|
@ -16021,6 +15926,7 @@
|
||||||
"version": "7.4.3",
|
"version": "7.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
|
||||||
"integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
|
"integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@isaacs/fs-minipass": "^4.0.0",
|
"@isaacs/fs-minipass": "^4.0.0",
|
||||||
|
@ -16667,6 +16573,7 @@
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz",
|
||||||
"integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==",
|
"integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"isexe": "^3.1.1"
|
"isexe": "^3.1.1"
|
||||||
|
@ -16972,6 +16879,7 @@
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
|
||||||
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
|
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
|
||||||
|
"dev": true,
|
||||||
"license": "BlueOak-1.0.0",
|
"license": "BlueOak-1.0.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
|
|
|
@ -592,6 +592,14 @@ export const webauthnChallenge = pgTable("webauthnChallenge", {
|
||||||
expiresAt: bigint("expiresAt", { mode: "number" }).notNull() // Unix timestamp
|
expiresAt: bigint("expiresAt", { mode: "number" }).notNull() // Unix timestamp
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const setupTokens = pgTable("setupTokens", {
|
||||||
|
tokenId: varchar("tokenId").primaryKey(),
|
||||||
|
token: varchar("token").notNull(),
|
||||||
|
used: boolean("used").notNull().default(false),
|
||||||
|
dateCreated: varchar("dateCreated").notNull(),
|
||||||
|
dateUsed: varchar("dateUsed")
|
||||||
|
});
|
||||||
|
|
||||||
export type Org = InferSelectModel<typeof orgs>;
|
export type Org = InferSelectModel<typeof orgs>;
|
||||||
export type User = InferSelectModel<typeof users>;
|
export type User = InferSelectModel<typeof users>;
|
||||||
export type Site = InferSelectModel<typeof sites>;
|
export type Site = InferSelectModel<typeof sites>;
|
||||||
|
@ -637,3 +645,4 @@ export type OlmSession = InferSelectModel<typeof olmSessions>;
|
||||||
export type UserClient = InferSelectModel<typeof userClients>;
|
export type UserClient = InferSelectModel<typeof userClients>;
|
||||||
export type RoleClient = InferSelectModel<typeof roleClients>;
|
export type RoleClient = InferSelectModel<typeof roleClients>;
|
||||||
export type OrgDomains = InferSelectModel<typeof orgDomains>;
|
export type OrgDomains = InferSelectModel<typeof orgDomains>;
|
||||||
|
export type SetupToken = InferSelectModel<typeof setupTokens>;
|
||||||
|
|
|
@ -187,6 +187,14 @@ export const webauthnChallenge = sqliteTable("webauthnChallenge", {
|
||||||
expiresAt: integer("expiresAt").notNull() // Unix timestamp
|
expiresAt: integer("expiresAt").notNull() // Unix timestamp
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const setupTokens = sqliteTable("setupTokens", {
|
||||||
|
tokenId: text("tokenId").primaryKey(),
|
||||||
|
token: text("token").notNull(),
|
||||||
|
used: integer("used", { mode: "boolean" }).notNull().default(false),
|
||||||
|
dateCreated: text("dateCreated").notNull(),
|
||||||
|
dateUsed: text("dateUsed")
|
||||||
|
});
|
||||||
|
|
||||||
export const newts = sqliteTable("newt", {
|
export const newts = sqliteTable("newt", {
|
||||||
newtId: text("id").primaryKey(),
|
newtId: text("id").primaryKey(),
|
||||||
secretHash: text("secretHash").notNull(),
|
secretHash: text("secretHash").notNull(),
|
||||||
|
@ -679,3 +687,4 @@ export type ApiKey = InferSelectModel<typeof apiKeys>;
|
||||||
export type ApiKeyAction = InferSelectModel<typeof apiKeyActions>;
|
export type ApiKeyAction = InferSelectModel<typeof apiKeyActions>;
|
||||||
export type ApiKeyOrg = InferSelectModel<typeof apiKeyOrg>;
|
export type ApiKeyOrg = InferSelectModel<typeof apiKeyOrg>;
|
||||||
export type OrgDomains = InferSelectModel<typeof orgDomains>;
|
export type OrgDomains = InferSelectModel<typeof orgDomains>;
|
||||||
|
export type SetupToken = InferSelectModel<typeof setupTokens>;
|
||||||
|
|
|
@ -10,6 +10,7 @@ export * from "./resetPassword";
|
||||||
export * from "./requestPasswordReset";
|
export * from "./requestPasswordReset";
|
||||||
export * from "./setServerAdmin";
|
export * from "./setServerAdmin";
|
||||||
export * from "./initialSetupComplete";
|
export * from "./initialSetupComplete";
|
||||||
|
export * from "./validateSetupToken";
|
||||||
export * from "./changePassword";
|
export * from "./changePassword";
|
||||||
export * from "./checkResourceSession";
|
export * from "./checkResourceSession";
|
||||||
export * from "./securityKey";
|
export * from "./securityKey";
|
||||||
|
|
|
@ -8,14 +8,15 @@ import logger from "@server/logger";
|
||||||
import { hashPassword } from "@server/auth/password";
|
import { hashPassword } from "@server/auth/password";
|
||||||
import { passwordSchema } from "@server/auth/passwordSchema";
|
import { passwordSchema } from "@server/auth/passwordSchema";
|
||||||
import { response } from "@server/lib";
|
import { response } from "@server/lib";
|
||||||
import { db, users } from "@server/db";
|
import { db, users, setupTokens } from "@server/db";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import { UserType } from "@server/types/UserTypes";
|
import { UserType } from "@server/types/UserTypes";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
|
||||||
export const bodySchema = z.object({
|
export const bodySchema = z.object({
|
||||||
email: z.string().toLowerCase().email(),
|
email: z.string().toLowerCase().email(),
|
||||||
password: passwordSchema
|
password: passwordSchema,
|
||||||
|
setupToken: z.string().min(1, "Setup token is required")
|
||||||
});
|
});
|
||||||
|
|
||||||
export type SetServerAdminBody = z.infer<typeof bodySchema>;
|
export type SetServerAdminBody = z.infer<typeof bodySchema>;
|
||||||
|
@ -39,7 +40,27 @@ export async function setServerAdmin(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { email, password } = parsedBody.data;
|
const { email, password, setupToken } = parsedBody.data;
|
||||||
|
|
||||||
|
// Validate setup token
|
||||||
|
const [validToken] = await db
|
||||||
|
.select()
|
||||||
|
.from(setupTokens)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(setupTokens.token, setupToken),
|
||||||
|
eq(setupTokens.used, false)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!validToken) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
"Invalid or expired setup token"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const [existing] = await db
|
const [existing] = await db
|
||||||
.select()
|
.select()
|
||||||
|
@ -58,7 +79,18 @@ export async function setServerAdmin(
|
||||||
const passwordHash = await hashPassword(password);
|
const passwordHash = await hashPassword(password);
|
||||||
const userId = generateId(15);
|
const userId = generateId(15);
|
||||||
|
|
||||||
await db.insert(users).values({
|
await db.transaction(async (trx) => {
|
||||||
|
// Mark the token as used
|
||||||
|
await trx
|
||||||
|
.update(setupTokens)
|
||||||
|
.set({
|
||||||
|
used: true,
|
||||||
|
dateUsed: moment().toISOString()
|
||||||
|
})
|
||||||
|
.where(eq(setupTokens.tokenId, validToken.tokenId));
|
||||||
|
|
||||||
|
// Create the server admin user
|
||||||
|
await trx.insert(users).values({
|
||||||
userId: userId,
|
userId: userId,
|
||||||
email: email,
|
email: email,
|
||||||
type: UserType.Internal,
|
type: UserType.Internal,
|
||||||
|
@ -68,6 +100,7 @@ export async function setServerAdmin(
|
||||||
serverAdmin: true,
|
serverAdmin: true,
|
||||||
emailVerified: true
|
emailVerified: true
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return response<SetServerAdminResponse>(res, {
|
return response<SetServerAdminResponse>(res, {
|
||||||
data: null,
|
data: null,
|
||||||
|
|
84
server/routers/auth/validateSetupToken.ts
Normal file
84
server/routers/auth/validateSetupToken.ts
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { db, setupTokens } from "@server/db";
|
||||||
|
import { eq, and } from "drizzle-orm";
|
||||||
|
import response from "@server/lib/response";
|
||||||
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
import createHttpError from "http-errors";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
|
const validateSetupTokenSchema = z
|
||||||
|
.object({
|
||||||
|
token: z.string().min(1, "Token is required")
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
|
export type ValidateSetupTokenResponse = {
|
||||||
|
valid: boolean;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function validateSetupToken(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
): Promise<any> {
|
||||||
|
try {
|
||||||
|
const parsedBody = validateSetupTokenSchema.safeParse(req.body);
|
||||||
|
|
||||||
|
if (!parsedBody.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedBody.error).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { token } = parsedBody.data;
|
||||||
|
|
||||||
|
// Find the token in the database
|
||||||
|
const [setupToken] = await db
|
||||||
|
.select()
|
||||||
|
.from(setupTokens)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(setupTokens.token, token),
|
||||||
|
eq(setupTokens.used, false)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!setupToken) {
|
||||||
|
return response<ValidateSetupTokenResponse>(res, {
|
||||||
|
data: {
|
||||||
|
valid: false,
|
||||||
|
message: "Invalid or expired setup token"
|
||||||
|
},
|
||||||
|
success: true,
|
||||||
|
error: false,
|
||||||
|
message: "Token validation completed",
|
||||||
|
status: HttpCode.OK
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return response<ValidateSetupTokenResponse>(res, {
|
||||||
|
data: {
|
||||||
|
valid: true,
|
||||||
|
message: "Setup token is valid"
|
||||||
|
},
|
||||||
|
success: true,
|
||||||
|
error: false,
|
||||||
|
message: "Token validation completed",
|
||||||
|
status: HttpCode.OK
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
"Failed to validate setup token"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1033,6 +1033,7 @@ authRouter.post("/idp/:idpId/oidc/validate-callback", idp.validateOidcCallback);
|
||||||
|
|
||||||
authRouter.put("/set-server-admin", auth.setServerAdmin);
|
authRouter.put("/set-server-admin", auth.setServerAdmin);
|
||||||
authRouter.get("/initial-setup-complete", auth.initialSetupComplete);
|
authRouter.get("/initial-setup-complete", auth.initialSetupComplete);
|
||||||
|
authRouter.post("/validate-setup-token", auth.validateSetupToken);
|
||||||
|
|
||||||
// Security Key routes
|
// Security Key routes
|
||||||
authRouter.post(
|
authRouter.post(
|
||||||
|
|
73
server/setup/ensureSetupToken.ts
Normal file
73
server/setup/ensureSetupToken.ts
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import { db, setupTokens, users } from "@server/db";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { generateRandomString, RandomReader } from "@oslojs/crypto/random";
|
||||||
|
import moment from "moment";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
|
||||||
|
const random: RandomReader = {
|
||||||
|
read(bytes: Uint8Array): void {
|
||||||
|
crypto.getRandomValues(bytes);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function generateToken(): string {
|
||||||
|
// Generate a 32-character alphanumeric token
|
||||||
|
const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
return generateRandomString(random, alphabet, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateId(length: number): string {
|
||||||
|
const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
return generateRandomString(random, alphabet, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function ensureSetupToken() {
|
||||||
|
try {
|
||||||
|
// Check if a server admin already exists
|
||||||
|
const [existingAdmin] = await db
|
||||||
|
.select()
|
||||||
|
.from(users)
|
||||||
|
.where(eq(users.serverAdmin, true));
|
||||||
|
|
||||||
|
// If admin exists, no need for setup token
|
||||||
|
if (existingAdmin) {
|
||||||
|
logger.warn("Server admin exists. Setup token generation skipped.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a setup token already exists
|
||||||
|
const existingTokens = await db
|
||||||
|
.select()
|
||||||
|
.from(setupTokens)
|
||||||
|
.where(eq(setupTokens.used, false));
|
||||||
|
|
||||||
|
// If unused token exists, display it instead of creating a new one
|
||||||
|
if (existingTokens.length > 0) {
|
||||||
|
console.log("=== SETUP TOKEN EXISTS ===");
|
||||||
|
console.log("Token:", existingTokens[0].token);
|
||||||
|
console.log("Use this token on the initial setup page");
|
||||||
|
console.log("================================");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a new setup token
|
||||||
|
const token = generateToken();
|
||||||
|
const tokenId = generateId(15);
|
||||||
|
|
||||||
|
await db.insert(setupTokens).values({
|
||||||
|
tokenId: tokenId,
|
||||||
|
token: token,
|
||||||
|
used: false,
|
||||||
|
dateCreated: moment().toISOString(),
|
||||||
|
dateUsed: null
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("=== SETUP TOKEN GENERATED ===");
|
||||||
|
console.log("Token:", token);
|
||||||
|
console.log("Use this token on the initial setup page");
|
||||||
|
console.log("================================");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to ensure setup token:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
import { ensureActions } from "./ensureActions";
|
import { ensureActions } from "./ensureActions";
|
||||||
import { copyInConfig } from "./copyInConfig";
|
import { copyInConfig } from "./copyInConfig";
|
||||||
import { clearStaleData } from "./clearStaleData";
|
import { clearStaleData } from "./clearStaleData";
|
||||||
|
import { ensureSetupToken } from "./ensureSetupToken";
|
||||||
|
|
||||||
export async function runSetupFunctions() {
|
export async function runSetupFunctions() {
|
||||||
await copyInConfig(); // copy in the config to the db as needed
|
await copyInConfig(); // copy in the config to the db as needed
|
||||||
await ensureActions(); // make sure all of the actions are in the db and the roles
|
await ensureActions(); // make sure all of the actions are in the db and the roles
|
||||||
await clearStaleData();
|
await clearStaleData();
|
||||||
|
await ensureSetupToken(); // ensure setup token exists for initial setup
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import path from "path";
|
||||||
import m1 from "./scriptsPg/1.6.0";
|
import m1 from "./scriptsPg/1.6.0";
|
||||||
import m2 from "./scriptsPg/1.7.0";
|
import m2 from "./scriptsPg/1.7.0";
|
||||||
import m3 from "./scriptsPg/1.8.0";
|
import m3 from "./scriptsPg/1.8.0";
|
||||||
|
import m4 from "./scriptsPg/1.9.0";
|
||||||
|
|
||||||
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
||||||
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
||||||
|
@ -16,7 +17,8 @@ import m3 from "./scriptsPg/1.8.0";
|
||||||
const migrations = [
|
const migrations = [
|
||||||
{ version: "1.6.0", run: m1 },
|
{ version: "1.6.0", run: m1 },
|
||||||
{ version: "1.7.0", run: m2 },
|
{ version: "1.7.0", run: m2 },
|
||||||
{ version: "1.8.0", run: m3 }
|
{ version: "1.8.0", run: m3 },
|
||||||
|
{ version: "1.9.0", run: m4 }
|
||||||
// Add new migrations here as they are created
|
// Add new migrations here as they are created
|
||||||
] as {
|
] as {
|
||||||
version: string;
|
version: string;
|
||||||
|
|
|
@ -25,6 +25,7 @@ import m20 from "./scriptsSqlite/1.5.0";
|
||||||
import m21 from "./scriptsSqlite/1.6.0";
|
import m21 from "./scriptsSqlite/1.6.0";
|
||||||
import m22 from "./scriptsSqlite/1.7.0";
|
import m22 from "./scriptsSqlite/1.7.0";
|
||||||
import m23 from "./scriptsSqlite/1.8.0";
|
import m23 from "./scriptsSqlite/1.8.0";
|
||||||
|
import m24 from "./scriptsSqlite/1.9.0";
|
||||||
|
|
||||||
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
||||||
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
||||||
|
@ -49,6 +50,7 @@ const migrations = [
|
||||||
{ version: "1.6.0", run: m21 },
|
{ version: "1.6.0", run: m21 },
|
||||||
{ version: "1.7.0", run: m22 },
|
{ version: "1.7.0", run: m22 },
|
||||||
{ version: "1.8.0", run: m23 },
|
{ version: "1.8.0", run: m23 },
|
||||||
|
{ version: "1.9.0", run: m24 },
|
||||||
// Add new migrations here as they are created
|
// Add new migrations here as they are created
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
|
25
server/setup/scriptsPg/1.9.0.ts
Normal file
25
server/setup/scriptsPg/1.9.0.ts
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { db } from "@server/db/pg/driver";
|
||||||
|
import { sql } from "drizzle-orm";
|
||||||
|
|
||||||
|
const version = "1.9.0";
|
||||||
|
|
||||||
|
export default async function migration() {
|
||||||
|
console.log(`Running setup script ${version}...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.execute(sql`
|
||||||
|
CREATE TABLE "setupTokens" (
|
||||||
|
"tokenId" varchar PRIMARY KEY NOT NULL,
|
||||||
|
"token" varchar NOT NULL,
|
||||||
|
"used" boolean DEFAULT false NOT NULL,
|
||||||
|
"dateCreated" varchar NOT NULL,
|
||||||
|
"dateUsed" varchar
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
console.log(`Added setupTokens table`);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Unable to add setupTokens table:", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
35
server/setup/scriptsSqlite/1.9.0.ts
Normal file
35
server/setup/scriptsSqlite/1.9.0.ts
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import { APP_PATH } from "@server/lib/consts";
|
||||||
|
import Database from "better-sqlite3";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
const version = "1.9.0";
|
||||||
|
|
||||||
|
export default async function migration() {
|
||||||
|
console.log(`Running setup script ${version}...`);
|
||||||
|
|
||||||
|
const location = path.join(APP_PATH, "db", "db.sqlite");
|
||||||
|
const db = new Database(location);
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.pragma("foreign_keys = OFF");
|
||||||
|
|
||||||
|
db.transaction(() => {
|
||||||
|
db.exec(`
|
||||||
|
CREATE TABLE 'setupTokens' (
|
||||||
|
'tokenId' text PRIMARY KEY NOT NULL,
|
||||||
|
'token' text NOT NULL,
|
||||||
|
'used' integer DEFAULT 0 NOT NULL,
|
||||||
|
'dateCreated' text NOT NULL,
|
||||||
|
'dateUsed' text
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
})();
|
||||||
|
|
||||||
|
db.pragma("foreign_keys = ON");
|
||||||
|
|
||||||
|
console.log(`Added setupTokens table`);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Unable to add setupTokens table:", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ import { passwordSchema } from "@server/auth/passwordSchema";
|
||||||
|
|
||||||
const formSchema = z
|
const formSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
setupToken: z.string().min(1, "Setup token is required"),
|
||||||
email: z.string().email({ message: "Invalid email address" }),
|
email: z.string().email({ message: "Invalid email address" }),
|
||||||
password: passwordSchema,
|
password: passwordSchema,
|
||||||
confirmPassword: z.string()
|
confirmPassword: z.string()
|
||||||
|
@ -52,6 +53,7 @@ export default function InitialSetupPage() {
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
setupToken: "",
|
||||||
email: "",
|
email: "",
|
||||||
password: "",
|
password: "",
|
||||||
confirmPassword: ""
|
confirmPassword: ""
|
||||||
|
@ -63,6 +65,7 @@ export default function InitialSetupPage() {
|
||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
const res = await api.put("/auth/set-server-admin", {
|
const res = await api.put("/auth/set-server-admin", {
|
||||||
|
setupToken: values.setupToken,
|
||||||
email: values.email,
|
email: values.email,
|
||||||
password: values.password
|
password: values.password
|
||||||
});
|
});
|
||||||
|
@ -102,6 +105,23 @@ export default function InitialSetupPage() {
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="space-y-4"
|
className="space-y-4"
|
||||||
>
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="setupToken"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>{t("setupToken")}</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder={t("setupTokenPlaceholder")}
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="email"
|
name="email"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue