Make control-plane the real mount authority

Split node enrollment from export sync and issue Finder-compatible DAV
credentials so the stack proves the real backend seam before any web UI
consumes it.
This commit is contained in:
Harivansh Rathi 2026-04-01 17:46:50 +00:00
parent 5bc24fa99d
commit b5f8ea9c52
28 changed files with 1345 additions and 423 deletions

View file

@ -66,6 +66,34 @@ paths:
description: Heartbeat accepted
"401":
description: Unauthorized
/api/v1/nodes/{nodeId}/exports:
put:
operationId: syncNodeExports
security:
- NodeToken: []
parameters:
- in: path
name: nodeId
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/NodeExportsRequest"
responses:
"200":
description: Export inventory accepted
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/StorageExport"
"401":
description: Unauthorized
/api/v1/exports:
get:
operationId: listExports
@ -213,7 +241,7 @@ components:
- displayName
- mountUrl
- readonly
- credentialMode
- credential
properties:
id:
type: string
@ -228,9 +256,25 @@ components:
type: string
readonly:
type: boolean
credentialMode:
credential:
$ref: "#/components/schemas/MountCredential"
MountCredential:
type: object
required:
- mode
- username
- password
- expiresAt
properties:
mode:
type: string
enum: [basic-auth]
username:
type: string
password:
type: string
expiresAt:
type: string
enum: [session-token, app-password]
CloudProfile:
type: object
required:
@ -287,7 +331,6 @@ components:
- agentVersion
- directAddress
- relayAddress
- exports
properties:
machineId:
type: string
@ -303,6 +346,11 @@ components:
type:
- string
- "null"
NodeExportsRequest:
type: object
required:
- exports
properties:
exports:
type: array
items:
@ -324,14 +372,8 @@ components:
MountProfileRequest:
type: object
required:
- userId
- deviceId
- exportId
properties:
userId:
type: string
deviceId:
type: string
exportId:
type: string
CloudProfileRequest:

View file

@ -0,0 +1,21 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://betternas.local/schemas/mount-credential.schema.json",
"type": "object",
"additionalProperties": false,
"required": ["mode", "username", "password", "expiresAt"],
"properties": {
"mode": {
"const": "basic-auth"
},
"username": {
"type": "string"
},
"password": {
"type": "string"
},
"expiresAt": {
"type": "string"
}
}
}

View file

@ -10,7 +10,7 @@
"displayName",
"mountUrl",
"readonly",
"credentialMode"
"credential"
],
"properties": {
"id": {
@ -31,8 +31,8 @@
"readonly": {
"type": "boolean"
},
"credentialMode": {
"enum": ["session-token", "app-password"]
"credential": {
"$ref": "./mount-credential.schema.json"
}
}
}

View file

@ -1,6 +1,7 @@
export const FOUNDATION_API_ROUTES = {
registerNode: "/api/v1/nodes/register",
nodeHeartbeat: "/api/v1/nodes/:nodeId/heartbeat",
nodeExports: "/api/v1/nodes/:nodeId/exports",
listExports: "/api/v1/exports",
issueMountProfile: "/api/v1/mount-profiles/issue",
issueCloudProfile: "/api/v1/cloud-profiles/issue",
@ -15,7 +16,7 @@ export type NasNodeStatus = "online" | "offline" | "degraded";
export type StorageAccessProtocol = "webdav";
export type AccessMode = "mount" | "cloud";
export type AccessPrincipalType = "user" | "device";
export type MountCredentialMode = "session-token" | "app-password";
export type MountCredentialMode = "basic-auth";
export type CloudProvider = "nextcloud";
export interface NasNode {
@ -56,7 +57,14 @@ export interface MountProfile {
displayName: string;
mountUrl: string;
readonly: boolean;
credentialMode: MountCredentialMode;
credential: MountCredential;
}
export interface MountCredential {
mode: MountCredentialMode;
username: string;
password: string;
expiresAt: string;
}
export interface CloudProfile {
@ -82,6 +90,9 @@ export interface NodeRegistrationRequest {
agentVersion: string;
directAddress: string | null;
relayAddress: string | null;
}
export interface NodeExportsRequest {
exports: StorageExportInput[];
}
@ -92,8 +103,6 @@ export interface NodeHeartbeatRequest {
}
export interface MountProfileRequest {
userId: string;
deviceId: string;
exportId: string;
}