# ###########################
# ╭──────────────────────╮ #
# │ _ │ #
diff --git a/authority/pom.xml b/authority/pom.xml
index 73515cf..e98636d 100644
--- a/authority/pom.xml
+++ b/authority/pom.xml
@@ -62,6 +62,39 @@
0.11.5
runtime
+
+
+
+
+
+ dev.samstevens.totp
+ totp-spring-boot-starter
+ 1.7.1
+
+
+
+
+ com.google.api-client
+ google-api-client
+ 2.4.0
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-jdbc
+
+
+ org.postgresql
+ postgresql
+ runtime
+
+
+
+
+ org.springframework.security
+ spring-security-crypto
+
diff --git a/authority/src/main/java/org/prole/authority/admin/AdminController.java b/authority/src/main/java/org/prole/authority/admin/AdminController.java
new file mode 100644
index 0000000..701f5a1
--- /dev/null
+++ b/authority/src/main/java/org/prole/authority/admin/AdminController.java
@@ -0,0 +1,116 @@
+package org.prole.authority.admin;
+
+import org.prole.authority.enroll.InviteService;
+import org.prole.authority.enroll.InviteService.InviteResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+/**
+ * AdminController — internal API for knoe.dev admin operations.
+ *
+ * Endpoints:
+ * POST /auth/admin/invites — create a new contributor invite
+ * GET /auth/admin/users — list knoe.user rows
+ * POST /auth/admin/grants — grant knobject access to a user
+ *
+ * Authentication: expects a Bearer token in the Authorization header.
+ * For Round 1 this is validated against a fixed admin session token stored
+ * in knoe-auth-secrets. Full RBAC is a Round 2 concern.
+ *
+ * NOTE: All endpoints return JSON. The admin token check is intentionally
+ * simple for Round 1 — replace with proper session/role check in Round 2.
+ */
+@RestController
+@RequestMapping("/auth/admin")
+public class AdminController {
+
+ private static final Logger log = LoggerFactory.getLogger(AdminController.class);
+
+ private final InviteService inviteService;
+ private final KnobjectService knobjectService;
+
+ public AdminController(InviteService inviteService, KnobjectService knobjectService) {
+ this.inviteService = inviteService;
+ this.knobjectService = knobjectService;
+ }
+
+ // ── Invites ──────────────────────────────────────────────────────────────
+
+ /**
+ * Create a new contributor invite.
+ *
+ * Request body: { "contact": "email@example.com", "contactType": "email", "nameHint": "..." }
+ * Response: { "token": "...", "enrollUrl": "...", "otp": "..." }
+ *
+ * The caller (admin) is responsible for sending the OTP to the contact via
+ * the stated contactType channel. The invite system itself does not send email.
+ */
+ @PostMapping(
+ path = "/invites",
+ consumes = MediaType.APPLICATION_JSON_VALUE,
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ public ResponseEntity