fix(auth): скрыть пароли от читателя и согласовать Portal
This commit is contained in:
1 parent
a15917d08b
commit
608f743003
20 files changed
+1412
-19
No files matched your search
@@ -1,6 +1,12 @@
|
||||
import { parseJsonArray } from "../../../db/json.js"
|
||||
import type { ServerRead, WanUplinkRead } from "../../../types/server.js"
|
||||
import type { ServerRow, SnapshotRow } from "../repository/servers-repository.js"
|
||||
import {hasPermission,type AuthUser} from "../../../lib/permissions.js"
|
||||
|
||||
/** Reading inventory does not grant access to credentials used by device operations. */
|
||||
export function serverReadForUser(server:ServerRead,user?:AuthUser):ServerRead {
|
||||
return !user || hasPermission(user.permissions,"mm:servers:write") ? server : {...server,password:""}
|
||||
}
|
||||
|
||||
function parseWanUplinksJson(raw: unknown): WanUplinkRead[] {
|
||||
const data = parseJsonArray(raw)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import {SignJWT} from 'jose'
|
||||
const database=process.env.MMAPP_OWN_SQL_DB
|
||||
if(!database)console.log('server secrets SQL: skipped; explicit owned database required')
|
||||
else{
|
||||
assert(/^mm_accept_166_\d+$/.test(database));assert.equal(new URL(process.env.DATABASE_URL!).pathname,'/'+database)
|
||||
process.env.AUTH_REQUIRED='true';process.env.AUTH_JWT_SECRET='owned196-server-read-secret';process.env.AUTH_ISSUER='https://owned196.test';process.env.NODE_ENV='test'
|
||||
const {pool,closePool}=await import('../db/index.js');const {initDatabase}=await import('../db/bootstrap.js');const {buildApp}=await import('../index.js')
|
||||
assert.equal((await pool.query('SELECT current_database() AS name')).rows[0].name,database)
|
||||
let serverId:number|undefined;const app=await buildApp({logger:false,startScheduler:false})
|
||||
try{
|
||||
await initDatabase()
|
||||
serverId=Number((await pool.query("INSERT INTO servers(name,host,enabled,username,password) VALUES('own196-secret','192.0.2.196',false,'fixture','own196-password') RETURNING id")).rows[0].id)
|
||||
for(const permission of ['mm:servers:read','mm:servers:write','mm:servers:admin']){
|
||||
const token=await new SignJWT({sub:'own196',apps:['mm'],permissions:[permission]}).setProtectedHeader({alg:'HS256'}).setIssuer('https://owned196.test').setExpirationTime('1h').sign(new TextEncoder().encode('owned196-server-read-secret'))
|
||||
for(const url of ['/api/servers','/api/servers/'+serverId]){
|
||||
const r=await app.inject({method:'GET',url,headers:{authorization:'Bearer '+token}});assert.equal(r.statusCode,200)
|
||||
const body=r.json(),row=Array.isArray(body)?body.find(s=>s.id===serverId):body
|
||||
assert.equal(row.password,permission.endsWith(':read')?'':'own196-password');assert.equal(row.username,'fixture')
|
||||
}
|
||||
}
|
||||
assert.equal((await pool.query('SELECT password FROM servers WHERE id=$1',[serverId])).rows[0].password,'own196-password','projection never changes stored credentials')
|
||||
const noToken=await app.inject({method:'GET',url:'/api/servers'});assert.equal(noToken.statusCode,401)
|
||||
console.log('server secrets owned SQL: reader/writer/admin list/detail and unchanged stored password passed')
|
||||
}finally{
|
||||
await app.close();if(serverId!==undefined)await pool.query('DELETE FROM servers WHERE id=$1',[serverId]);await closePool()
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
updateServer,
|
||||
} from "../modules/servers/service/servers-service.js"
|
||||
import { getServerRowById } from "../modules/servers/repository/servers-repository.js"
|
||||
import {serverReadForUser} from "../modules/servers/mapper/servers-mapper.js"
|
||||
|
||||
// ── plugin ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -76,8 +77,8 @@ const serversRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
})
|
||||
|
||||
// GET /api/servers
|
||||
app.get("/", async (_req, reply) => {
|
||||
return reply.send(await listServersRead())
|
||||
app.get("/", async (req, reply) => {
|
||||
return reply.send((await listServersRead()).map(server=>serverReadForUser(server,req.authUser)))
|
||||
})
|
||||
|
||||
// GET /api/servers/:id/ros-src-address — IPv4 для src-address в RouterOS (не FQDN)
|
||||
@@ -238,7 +239,7 @@ const serversRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
const params = req.params as ServerIdParams
|
||||
const server = await getServerReadById(params.id)
|
||||
if (!server) return reply.status(404).send({ error: "Server not found" })
|
||||
return reply.send(server)
|
||||
return reply.send(serverReadForUser(server,req.authUser))
|
||||
})
|
||||
|
||||
// PUT /api/servers/:id
|
||||
|
||||
Reference in new issue
Block a user