Skip to content

Commit 7623abc

Browse files
authored
Merge pull request #479 from BrentIO/feat/477
Replace provisioning bundle endpoint with per-device token flow
2 parents ef5d0d2 + 1c92dd6 commit 7623abc

6 files changed

Lines changed: 468 additions & 535 deletions

File tree

Controller/Controller.ino

Lines changed: 324 additions & 369 deletions
Large diffs are not rendered by default.

Controller/openapi.yaml

Lines changed: 71 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -441,141 +441,53 @@ paths:
441441
$ref: '#/components/schemas/error_message'
442442

443443

444-
/api/provisioning/nonce:
445-
get:
444+
/api/provisioning/token:
445+
post:
446446
tags:
447447
- Controllers
448-
summary: Retrieve a single-use session nonce for client provisioning
449-
description: Returns a cryptographically random single-use nonce. No authentication required. Returns 409 if provisioning mode is not active. The nonce is regenerated on each call.
448+
summary: Exchange credentials for a per-device provisioning token
449+
description: >
450+
Authenticates an unprovisioned controller by UUID and MAC address, and returns a short-lived
451+
bearer token for use in subsequent provisioning requests. The token is bound to the requesting
452+
device's MAC address and has a sliding expiry window (5 minutes in production, 30 minutes in
453+
debug builds). A second POST for the same MAC invalidates the previous token. Only accessible
454+
via the provisioning SoftAP — Ethernet requests are rejected.
455+
requestBody:
456+
required: true
457+
content:
458+
application/json:
459+
schema:
460+
type: object
461+
required:
462+
- uuid
463+
- mac_address
464+
properties:
465+
uuid:
466+
$ref: '#/components/schemas/uuid'
467+
mac_address:
468+
$ref: '#/components/schemas/macAddress'
469+
description: MAC address of the requesting controller. Must match the mac field stored in the controller record.
450470
responses:
451471
'200':
452-
description: OK
472+
description: OK — token issued
453473
content:
454474
application/json:
455475
schema:
456476
type: object
457477
properties:
458-
nonce:
478+
token:
459479
type: integer
460480
format: int64
461-
description: Single-use session nonce; must be passed as the x-nonce header in the subsequent GET /api/provisioning/client call
481+
description: Per-device provisioning token. Pass as the provisioning-token header in subsequent GET requests.
462482
example: 3819273645
463-
'403':
464-
description: Forbidden — request did not arrive via the provisioning SoftAP network
465-
content:
466-
application/json:
467-
schema:
468-
$ref: '#/components/schemas/error_message'
469-
example:
470-
message: Only accessible via provisioning network
471-
'405':
472-
description: Method Not Allowed
473-
'409':
474-
description: Conflict
475-
content:
476-
application/json:
477-
schema:
478-
$ref: '#/components/schemas/error_message'
479-
example:
480-
message: Provisioning mode inactive
481-
482-
/api/provisioning/client:
483-
get:
484-
tags:
485-
- Controllers
486-
summary: Retrieve a client configuration during active provisioning
487-
description: Returns the full client configuration JSON for the device identified by the mac-address header. Requires provisioning mode to be active, a valid unused nonce, and a MAC address that matches a registered client record. The nonce is invalidated after a successful response.
488-
security:
489-
- macAddress: []
490-
parameters:
491-
- in: header
492-
name: x-nonce
493-
required: true
494-
schema:
495-
type: integer
496-
format: int64
497-
description: Single-use nonce obtained from GET /api/provisioning/nonce
498-
responses:
499-
'200':
500-
description: OK
501-
content:
502-
application/json:
503-
schema:
504-
$ref: '#/components/schemas/client'
505-
'401':
506-
description: Unauthorized — missing or invalid mac-address or x-nonce header
507-
'403':
508-
description: Forbidden — request did not arrive via the provisioning SoftAP network, or MAC address not in the provisioning allowlist
509-
content:
510-
application/json:
511-
schema:
512-
$ref: '#/components/schemas/error_message'
513-
example:
514-
message: Request will not be fulfilled
515-
'404':
516-
description: Not Found — no client record matches the provided MAC address
517-
'405':
518-
description: Method Not Allowed
519-
'409':
520-
description: Conflict
521-
content:
522-
application/json:
523-
schema:
524-
$ref: '#/components/schemas/error_message'
525-
example:
526-
message: Provisioning mode inactive
527-
'500':
528-
description: Internal Server Error
529-
content:
530-
application/json:
531-
schema:
532-
$ref: '#/components/schemas/error_message'
533-
example:
534-
message: File system not mounted
535-
536-
/api/provisioning/controller:
537-
get:
538-
tags:
539-
- Controllers
540-
summary: Retrieve all controller and client records during active provisioning
541-
description: Returns a JSON bundle containing all controller records and all client records stored on the source controller. Intended for controller-to-controller provisioning — an unprovisioned controller connecting via the provisioning SoftAP uses this endpoint to pull the full configuration. Requires provisioning mode to be active, a valid unused nonce, and a MAC address that matches a registered controller record. The nonce is invalidated after a successful response.
542-
security:
543-
- macAddress: []
544-
parameters:
545-
- in: header
546-
name: x-nonce
547-
required: true
548-
schema:
549-
type: integer
550-
format: int64
551-
description: Single-use nonce obtained from GET /api/provisioning/nonce
552-
- in: header
553-
name: x-device-uuid
554-
required: false
555-
schema:
556-
type: string
557-
format: uuid
558-
description: UUID of the requesting controller. When provided, the host attempts a direct file lookup by UUID and verifies the MAC matches before falling back to full enumeration.
559-
responses:
560-
'200':
561-
description: OK
483+
'400':
484+
description: Bad Request — uuid must be 36 characters, or mac_address is missing
562485
content:
563486
application/json:
564487
schema:
565-
type: object
566-
properties:
567-
controllers:
568-
type: array
569-
description: All controller records stored on the source controller, each augmented with a uuid field set to the record's filename
570-
items:
571-
$ref: '#/components/schemas/controller'
572-
clients:
573-
type: array
574-
description: All client records stored on the source controller, each augmented with a uuid field set to the record's filename
575-
items:
576-
$ref: '#/components/schemas/client'
488+
$ref: '#/components/schemas/bad_request_message'
577489
'401':
578-
description: Unauthorized — missing or invalid mac-address or x-nonce header
490+
description: Unauthorized — MAC address does not match the stored controller record
579491
'403':
580492
description: Forbidden — request did not arrive via the provisioning SoftAP network
581493
content:
@@ -585,11 +497,11 @@ paths:
585497
example:
586498
message: Only accessible via provisioning network
587499
'404':
588-
description: Not Found — no controller record matches the provided MAC address
500+
description: Not Found — no controller record found for the provided UUID, or the record has no mac field
589501
'405':
590502
description: Method Not Allowed
591503
'409':
592-
description: Conflict
504+
description: Conflict — provisioning mode is not active
593505
content:
594506
application/json:
595507
schema:
@@ -602,8 +514,6 @@ paths:
602514
application/json:
603515
schema:
604516
$ref: '#/components/schemas/error_message'
605-
example:
606-
message: File system not mounted
607517

608518

609519
/api/provisioning/certs:
@@ -682,6 +592,7 @@ paths:
682592
description: Retrieve the list of defined controller UUIDs
683593
security:
684594
- visualToken: []
595+
- provisioningToken: []
685596
responses:
686597
'200':
687598
description: OK
@@ -710,6 +621,7 @@ paths:
710621
description: Retrieve a controller
711622
security:
712623
- visualToken: []
624+
- provisioningToken: []
713625
parameters:
714626
- in: path
715627
name: uuid
@@ -817,6 +729,7 @@ paths:
817729
description: Retrieve the list of defined client UUIDs
818730
security:
819731
- visualToken: []
732+
- provisioningToken: []
820733
responses:
821734
'200':
822735
description: OK
@@ -845,7 +758,7 @@ paths:
845758
description: Retrieve a client
846759
security:
847760
- visualToken: []
848-
- macAddress: []
761+
- provisioningToken: []
849762
parameters:
850763
- in: path
851764
name: uuid
@@ -860,26 +773,26 @@ paths:
860773
application/json:
861774
schema:
862775
$ref: '#/components/schemas/client'
863-
'400':
864-
description: Bad Request
865-
content:
866-
application/json:
867-
schema:
868-
$ref: '#/components/schemas/error_message'
869-
example:
870-
message: Provisioning mode inactive
871776
'401':
872777
description: Unauthorized
873778
'403':
874-
description: Forbidden
779+
description: Forbidden — provisioning-token request did not arrive via the provisioning SoftAP network
875780
content:
876781
application/json:
877782
schema:
878783
$ref: '#/components/schemas/error_message'
879784
example:
880-
message: Request will not be fulfilled
785+
message: Only accessible via provisioning network
881786
'404':
882787
description: Not Found
788+
'409':
789+
description: Conflict — provisioning-token used but provisioning mode is not active
790+
content:
791+
application/json:
792+
schema:
793+
$ref: '#/components/schemas/error_message'
794+
example:
795+
message: Provisioning mode inactive
883796
'500':
884797
description: Internal Server Error
885798
content:
@@ -995,6 +908,7 @@ paths:
995908
description: Retrieves the backup of the database in Dexie format.
996909
security:
997910
- visualToken: []
911+
- provisioningToken: []
998912
responses:
999913
'200':
1000914
description: OK
@@ -1009,8 +923,24 @@ paths:
1009923
$ref: '#/components/schemas/dexieFormat'
1010924
'401':
1011925
description: Unauthorized
926+
'403':
927+
description: Forbidden — provisioning-token request did not arrive via the provisioning SoftAP network
928+
content:
929+
application/json:
930+
schema:
931+
$ref: '#/components/schemas/error_message'
932+
example:
933+
message: Only accessible via provisioning network
1012934
'404':
1013935
description: Not Found
936+
'409':
937+
description: Conflict — provisioning-token used but provisioning mode is not active
938+
content:
939+
application/json:
940+
schema:
941+
$ref: '#/components/schemas/error_message'
942+
example:
943+
message: Provisioning mode inactive
1014944
'500':
1015945
description: Internal Server Error
1016946
content:
@@ -1236,7 +1166,13 @@ components:
12361166
type: apiKey
12371167
in: header
12381168
name: mac-address
1239-
description: A client MAC address that matches the resource being requested, valid only while in provisoining mode
1169+
description: A client MAC address that matches the resource being requested, valid only while in provisioning mode
1170+
1171+
provisioningToken:
1172+
type: apiKey
1173+
in: header
1174+
name: provisioning-token
1175+
description: A per-device token issued by POST /api/provisioning/token, valid while provisioning mode is active. Must be used via the provisioning SoftAP; tokens are refused on the Ethernet interface.
12401176

12411177

12421178
schemas:

Controller/ui/src/composables/usePayloads.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ export async function buildControllerPayload(controllerId, returnOnlyErrors = fa
147147
const payload = {
148148
name: controller.name.trim().substring(0, MAX_NAME),
149149
area: area.name.trim().substring(0, MAX_AREA),
150+
mac: controller.mac,
150151
ports,
151152
outputs,
152153
mqtt: mqttPayload

common/eventLog.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
uint16_t _head = 0; /* Index of the oldest entry in the circular buffer */
3333
uint16_t _count = 0; /* Number of valid entries currently stored */
3434

35-
LinkedList<const char*> _errors;
35+
LinkedList<String> _errors;
3636
NTPClient* _timeClient;
3737

3838
void (*_ptrInfoCallback)(); //Function to call when there is an info logged
@@ -47,7 +47,7 @@
4747
void _logError(const char* text){
4848

4949
for(int i=0; i < this->_errors.size(); i++){
50-
if(strcmp(this->_errors.get(i), text) == 0){
50+
if(this->_errors.get(i) == text){
5151
return;
5252
}
5353
}
@@ -56,7 +56,7 @@
5656
this->_errors.shift();
5757
}
5858

59-
this->_errors.add(text);
59+
this->_errors.add(String(text));
6060
}
6161

6262
public:
@@ -190,7 +190,7 @@
190190
/**
191191
* Retrieves the list of errors from the error log
192192
*/
193-
LinkedList<const char*>* getErrors(){
193+
LinkedList<String>* getErrors(){
194194
return &_errors;
195195
}
196196

@@ -204,7 +204,7 @@
204204

205205
for(int i=0; i < this->_errors.size(); i++){
206206

207-
if(strcmp(this->_errors.get(i), text) == 0){
207+
if(this->_errors.get(i) == text){
208208
this->_errors.remove(i);
209209

210210
if(this->_ptrResolvedErrorCallback){

common/oled.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -981,7 +981,7 @@
981981
}
982982

983983
for(int8_t i = iteratorOledStart; i < this->_eventLog->getErrors()->size(); i++){
984-
this->_printAsciiLine(this->_eventLog->getErrors()->get(i));
984+
this->_printAsciiLine(this->_eventLog->getErrors()->get(i).c_str());
985985
}
986986
}
987987
#endif

0 commit comments

Comments
 (0)