diff --git a/heicode/router/api-router.go b/heicode/router/api-router.go index 030e9102..833ff1da 100644 --- a/heicode/router/api-router.go +++ b/heicode/router/api-router.go @@ -326,13 +326,28 @@ func SetApiRouter(router *gin.Engine) { // model, and the device-pair flow doesn't accidentally show // up in the legacy /api/token UI. CriticalRateLimit on the // pair endpoint stops brute-force device-spam from one user. - deviceRoute := apiRouter.Group("/devices") - deviceRoute.Use(middleware.UserAuth()) + // V2 first-launch pairing accepts EITHER a user JWT (credentials + // login flow) OR a sk- access token (OAuth-redirect flow). The + // OAuth callback hands cc-haha only a sk-, so the original + // UserAuth-only path made pair 401 and the device-binding flow + // failed silently. We split /pair onto its own group with + // TokenOrUserAuth, while keeping the read/manage routes behind + // UserAuth (a sk- bearer must NOT be allowed to enumerate or + // revoke the user's other devices). + deviceManageRoute := apiRouter.Group("/devices") + deviceManageRoute.Use(middleware.UserAuth()) { - deviceRoute.GET("/", controller.ListUserDevices) - deviceRoute.POST("/pair", middleware.CriticalRateLimit(), controller.PairDevice) - deviceRoute.PATCH("/:id", controller.RenameUserDevice) - deviceRoute.DELETE("/:id", controller.RevokeUserDevice) + deviceManageRoute.GET("/", controller.ListUserDevices) + deviceManageRoute.PATCH("/:id", controller.RenameUserDevice) + deviceManageRoute.DELETE("/:id", controller.RevokeUserDevice) + } + devicePairRoute := apiRouter.Group("/devices") + devicePairRoute.Use(middleware.TokenOrUserAuth()) + { + devicePairRoute.POST("/pair", + middleware.CriticalRateLimit(), + controller.PairDevice, + ) } usageRoute := apiRouter.Group("/usage")