diff --git a/cmd/devices-api/api.go b/cmd/devices-api/api.go index cbc67e220..7cd2a6885 100644 --- a/cmd/devices-api/api.go +++ b/cmd/devices-api/api.go @@ -253,6 +253,8 @@ func startWebAPI(logger zerolog.Logger, settings *config.Settings, pdb db.Store, sdAuth.Get("/:syntheticDeviceNode/burn", syntheticController.GetSyntheticDeviceBurnPayload) sdAuth.Post("/:syntheticDeviceNode/burn", syntheticController.BurnSyntheticDevice) + + sdAuth.Post("/:syntheticDeviceNode/re-authenticate", syntheticController.ReAuthenticate) } // Vehicle owner routes. diff --git a/docs/docs.go b/docs/docs.go index 6b65315e1..5351db679 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,4 +1,4 @@ -// Code generated by swaggo/swag at 2023-06-28 14:32:56.589045 -0400 EDT m=+1.536088376. DO NOT EDIT. +// Code generated by swaggo/swag at 2023-07-20 21:19:37.558189 -0400 EDT m=+1.773723251. DO NOT EDIT. package docs @@ -679,6 +679,28 @@ const docTemplate = `{ } } }, + "/synthetic/device/{syntheticDeviceNode}/re-authenticate": { + "post": { + "description": "Submit new credentials for the given synthetic device. Mostly\nused in the event of an upstream password change.", + "produces": [ + "application/json" + ], + "parameters": [ + { + "type": "integer", + "description": "synthetic device token id", + "name": "syntheticDeviceNode", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/user/devices": { "post": { "security": [ diff --git a/docs/swagger.json b/docs/swagger.json index 0bfc69f0a..a57cbe86e 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -670,6 +670,28 @@ } } }, + "/synthetic/device/{syntheticDeviceNode}/re-authenticate": { + "post": { + "description": "Submit new credentials for the given synthetic device. Mostly\nused in the event of an upstream password change.", + "produces": [ + "application/json" + ], + "parameters": [ + { + "type": "integer", + "description": "synthetic device token id", + "name": "syntheticDeviceNode", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/user/devices": { "post": { "security": [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 7ccba44ab..271bc4b4d 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1366,6 +1366,22 @@ paths: responses: "200": description: OK + /synthetic/device/{syntheticDeviceNode}/re-authenticate: + post: + description: |- + Submit new credentials for the given synthetic device. Mostly + used in the event of an upstream password change. + parameters: + - description: synthetic device token id + in: path + name: syntheticDeviceNode + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK /synthetic/device/mint/{integrationNode}/{vehicleNode}: get: description: |- diff --git a/internal/controllers/synthetic_devices_controller.go b/internal/controllers/synthetic_devices_controller.go index 3fe6ddc35..8c556fe57 100644 --- a/internal/controllers/synthetic_devices_controller.go +++ b/internal/controllers/synthetic_devices_controller.go @@ -44,9 +44,14 @@ type SyntheticDevicesController struct { usersClient pb.UserServiceClient walletSvc services.SyntheticWalletInstanceService registryClient registry.Client + + cipher shared.Cipher + smartcarClient services.SmartcarClient - teslaService services.TeslaService - cipher shared.Cipher + smartcarTask services.SmartcarTaskService + + teslaService services.TeslaService + teslaTask services.TeslaTaskService } type MintSyntheticDeviceRequest struct { @@ -55,12 +60,15 @@ type MintSyntheticDeviceRequest struct { } type credentials struct { + // Smartcar + Code string `json:"code"` + RedirectURI string `json:"redirectUri"` + + // Tesla + ID string `json:"id"` AccessToken string `json:"accessToken"` RefreshToken string `json:"refreshToken"` ExpiresIn int64 `json:"expiresIn"` - ExternalID string `json:"externalId"` - Code string `json:"code"` - RedirectURI string `json:"redirectUri"` } type SyntheticDeviceSequence struct { @@ -68,8 +76,16 @@ type SyntheticDeviceSequence struct { } func NewSyntheticDevicesController( - settings *config.Settings, dbs func() *db.ReaderWriter, logger *zerolog.Logger, deviceDefSvc services.DeviceDefinitionService, usersClient pb.UserServiceClient, walletSvc services.SyntheticWalletInstanceService, registryClient registry.Client, smartcarClient services.SmartcarClient, teslaSvc services.TeslaService, cipher shared.Cipher, - + settings *config.Settings, + dbs func() *db.ReaderWriter, + logger *zerolog.Logger, + deviceDefSvc services.DeviceDefinitionService, + usersClient pb.UserServiceClient, + walletSvc services.SyntheticWalletInstanceService, + registryClient registry.Client, + smartcarClient services.SmartcarClient, + teslaSvc services.TeslaService, + cipher shared.Cipher, ) SyntheticDevicesController { return SyntheticDevicesController{ Settings: settings, @@ -450,9 +466,9 @@ func (sdc *SyntheticDevicesController) generateUDAI(ctx context.Context, creds c udi.Metadata = null.JSONFrom(mb) udi.ExternalID = null.StringFrom(externalID) case constants.TeslaVendor: - teslaID, err := strconv.Atoi(creds.ExternalID) + teslaID, err := strconv.Atoi(creds.ID) if err != nil { - sdc.log.Err(err).Msgf("unable to parse external id %q as integer", creds.ExternalID) + sdc.log.Err(err).Msgf("unable to parse external id %q as integer", creds.ID) return nil, err } @@ -482,7 +498,7 @@ func (sdc *SyntheticDevicesController) generateUDAI(ctx context.Context, creds c mb, _ := json.Marshal(meta) - udi.ExternalID = null.StringFrom(creds.ExternalID) + udi.ExternalID = null.StringFrom(creds.ID) udi.AccessExpiresAt = null.TimeFrom(time.Now().Add(time.Duration(creds.ExpiresIn) * time.Second)) udi.Metadata = null.JSONFrom(mb) default: @@ -647,3 +663,92 @@ func (sdc *SyntheticDevicesController) BurnSyntheticDevice(c *fiber.Ctx) error { return err } + +// ReAuthenticate godoc +// @Description Submit new credentials for the given synthetic device. Mostly +// @Description used in the event of an upstream password change. +// @Produce json +// @Param syntheticDeviceNode path int true "synthetic device token id" +// @Success 200 +// @Router /synthetic/device/{syntheticDeviceNode}/re-authenticate [post] +func (sdc *SyntheticDevicesController) ReAuthenticate(c *fiber.Ctx) error { + syntheticDeviceNodeRaw := c.Params("syntheticDeviceNode") + // userID := helpers.GetUserID(c) + + sdn, err := strconv.ParseInt(syntheticDeviceNodeRaw, 10, 64) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Couldn't parse the given token id.") + } + + sd, err := models.SyntheticDevices( + models.SyntheticDeviceWhere.TokenID.EQ(types.NewNullDecimal(decimal.New(sdn, 0))), + qm.Load(models.SyntheticDeviceRels.VehicleToken), + ).One(c.Context(), sdc.DBS().Reader) + if err != nil { + if err == sql.ErrNoRows { + return fiber.NewError(fiber.StatusNotFound, "No synthetic device with that id.") + } + return err + } + + if !sd.R.VehicleToken.UserDeviceID.Valid { + return fiber.NewError(fiber.StatusConflict, "Vehicle deleted.") + } + + iNode, _ := sd.IntegrationTokenID.Uint64() + + i, err := sdc.deviceDefSvc.GetIntegrationByTokenID(c.Context(), iNode) + if err != nil { + return err + } + + oldUDAI, err := models.FindUserDeviceAPIIntegration(c.Context(), sdc.DBS().Reader, sd.R.VehicleToken.UserDeviceID.String, i.Id) + if err != nil { + return err + } + + // Should really create a new type. + var req MintSyntheticDeviceRequest + if err := c.BodyParser(&req); err != nil { + return fiber.NewError(fiber.StatusBadRequest, "Couldn't parse request.") + } + + newUDAI, err := sdc.generateUDAI(c.Context(), req.Credentials, sd.R.VehicleToken.UserDeviceID.String, i) + if err != nil { + return err + } + + switch i.Vendor { + case constants.SmartCarVendor: + if err := sdc.smartcarTask.StopPoll(oldUDAI); err != nil { + return err + } + if err := sdc.smartcarTask.StartPoll(newUDAI); err != nil { + return err + } + if _, err := newUDAI.Update(c.Context(), sdc.DBS().Writer, boil.Infer()); err != nil { + return err + } + case constants.TeslaVendor: + // Awful + id, err := strconv.Atoi(newUDAI.ExternalID.String) + if err != nil { + return err + } + var md services.UserDeviceAPIIntegrationsMetadata + if err := newUDAI.Metadata.Unmarshal(&md); err != nil { + return err + } + if err := sdc.teslaTask.StopPoll(oldUDAI); err != nil { + return err + } + if err := sdc.teslaTask.StartPoll(&services.TeslaVehicle{ID: id, VehicleID: md.TeslaVehicleID}, newUDAI); err != nil { + return err + } + if _, err := newUDAI.Update(c.Context(), sdc.DBS().Writer, boil.Infer()); err != nil { + return err + } + } + + return nil +} diff --git a/internal/controllers/synthetic_devices_controller_test.go b/internal/controllers/synthetic_devices_controller_test.go index 026bee848..16b3a9530 100644 --- a/internal/controllers/synthetic_devices_controller_test.go +++ b/internal/controllers/synthetic_devices_controller_test.go @@ -50,7 +50,7 @@ var teslaClientToken = MintSyntheticDeviceRequest{ Credentials: credentials{ AccessToken: "accessToken", RefreshToken: "refreshToken", - ExternalID: "13", + ID: "13", ExpiresIn: 28800, }, } diff --git a/internal/services/models.go b/internal/services/models.go index fe9521de9..51ca76263 100644 --- a/internal/services/models.go +++ b/internal/services/models.go @@ -147,7 +147,7 @@ type UserDeviceAPIIntegrationsMetadata struct { Commands *UserDeviceAPIIntegrationsMetadataCommands `json:"commands,omitempty"` // CANProtocol is the protocol that was detected by edge-network from the autopi. CANProtocol *string `json:"canProtocol,omitempty"` - TeslaVehicleID int `json:"teslaVehicleID,omitempty"` + TeslaVehicleID int `json:"teslaVehicleId,omitempty"` } type UserDeviceAPIIntegrationsMetadataCommands struct {