diff --git a/app/handlers/site.go b/app/handlers/site.go index dde2292..dd6055d 100644 --- a/app/handlers/site.go +++ b/app/handlers/site.go @@ -6,6 +6,7 @@ import ( "log" "quay/app/models" "quay/app/repository" + "quay/internal/security" "github.com/gofiber/fiber/v3" ) @@ -145,7 +146,7 @@ func validateIncomingSite(site *models.Site) error { // @Accept json // @Produce json // @Param site body models.Site true "Site details" -// @Success 200 {object} models.Site +// @Success 200 {object} models.CreateSiteResponse // @Failure 400 {object} models.APIError // @Failure 500 {object} models.APIError // @Router /sites [post] @@ -165,6 +166,17 @@ func (h *SiteHandler) PostSite(c fiber.Ctx) error { }) } + rawDeployToken, hashedDeployToken, err := security.CreateDeployToken() + + if err != nil { + log.Println("Error creating deploy token: ", err) + return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{ + Message: "Unexpected error while creating deploy token: " + err.Error(), + }) + } + + site.DeployToken = hashedDeployToken + if err := h.Repo.CreateSite(&site); err != nil { log.Println("Error creating site: ", err) return c.Status(fiber.StatusInternalServerError).JSON(&models.APIError{ @@ -172,7 +184,10 @@ func (h *SiteHandler) PostSite(c fiber.Ctx) error { }) } - return c.JSON(site) + return c.JSON(&models.CreateSiteResponse{ + Site: site, + RawDeployToken: rawDeployToken, + }) } // PutSite godoc diff --git a/app/models/site.go b/app/models/site.go index 8b49fd7..3faa3fa 100644 --- a/app/models/site.go +++ b/app/models/site.go @@ -39,3 +39,8 @@ type GetAllSitesResponse struct { Sites []Site `json:"sites"` Total int `json:"total"` } + +type CreateSiteResponse struct { + Site Site `json:"site"` + RawDeployToken string `json:"raw_deploy_token"` +} diff --git a/docs/docs.go b/docs/docs.go index 2b551bc..5486b18 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -573,7 +573,7 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.Site" + "$ref": "#/definitions/models.CreateSiteResponse" } }, "400": { @@ -943,6 +943,17 @@ const docTemplate = `{ } } }, + "models.CreateSiteResponse": { + "type": "object", + "properties": { + "raw_deploy_token": { + "type": "string" + }, + "site": { + "$ref": "#/definitions/models.Site" + } + } + }, "models.CustomHeaders": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index be9b25a..770f61c 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -567,7 +567,7 @@ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/models.Site" + "$ref": "#/definitions/models.CreateSiteResponse" } }, "400": { @@ -937,6 +937,17 @@ } } }, + "models.CreateSiteResponse": { + "type": "object", + "properties": { + "raw_deploy_token": { + "type": "string" + }, + "site": { + "$ref": "#/definitions/models.Site" + } + } + }, "models.CustomHeaders": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 9d4ef56..715156a 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -5,6 +5,13 @@ definitions: message: type: string type: object + models.CreateSiteResponse: + properties: + raw_deploy_token: + type: string + site: + $ref: '#/definitions/models.Site' + type: object models.CustomHeaders: properties: headers: @@ -456,7 +463,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/models.Site' + $ref: '#/definitions/models.CreateSiteResponse' "400": description: Bad Request schema: diff --git a/internal/security/deploy_token.go b/internal/security/deploy_token.go new file mode 100644 index 0000000..16da0f2 --- /dev/null +++ b/internal/security/deploy_token.go @@ -0,0 +1,40 @@ +package security + +import ( + "crypto/rand" + "crypto/sha256" + "crypto/subtle" + "encoding/hex" + "fmt" +) + +func generateToken() (rawToken string, err error) { + bytes := make([]byte, 32) + if _, err = rand.Read(bytes); err != nil { + return "", err + } + return "quay_" + hex.EncodeToString(bytes), nil +} + +func hashToken(rawToken string) string { + sum := sha256.Sum256([]byte(rawToken)) + return hex.EncodeToString(sum[:]) +} + +func CreateDeployToken() (rawToken, hashedToken string, err error) { + rawToken, err = generateToken() + if err != nil { + return "", "", fmt.Errorf("failed to generate token: %w", err) + } + hashedToken = hashToken(rawToken) + return rawToken, hashedToken, nil +} + +func CompareDeployTokens(incomingRawToken, storedHashedToken string) bool { + incomingHash := hashToken(incomingRawToken) + + return subtle.ConstantTimeCompare( + []byte(incomingHash), + []byte(storedHashedToken), + ) == 1 +}