From 8b379b7466ea6a3cb2f9d91b28c16d450f6a5987 Mon Sep 17 00:00:00 2001 From: kolaente Date: Thu, 26 Mar 2026 16:32:01 +0100 Subject: [PATCH] feat: add OAuth 2.0 authorize endpoint Add POST /api/v1/oauth/authorize behind auth middleware. Validates OAuth parameters (response_type, redirect_uri, PKCE), fetches the authenticated user, creates an authorization code, and returns it as JSON for the frontend to handle the redirect. --- pkg/modules/auth/oauth2server/authorize.go | 100 +++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 pkg/modules/auth/oauth2server/authorize.go diff --git a/pkg/modules/auth/oauth2server/authorize.go b/pkg/modules/auth/oauth2server/authorize.go new file mode 100644 index 000000000..873c00900 --- /dev/null +++ b/pkg/modules/auth/oauth2server/authorize.go @@ -0,0 +1,100 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-present Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package oauth2server + +import ( + "net/http" + + "code.vikunja.io/api/pkg/db" + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/user" + + "github.com/labstack/echo/v5" +) + +// authorizeRequest represents the JSON body for the authorize endpoint. +type authorizeRequest struct { + ResponseType string `json:"response_type"` + ClientID string `json:"client_id"` + RedirectURI string `json:"redirect_uri"` + State string `json:"state"` + CodeChallenge string `json:"code_challenge"` + CodeChallengeMethod string `json:"code_challenge_method"` +} + +// AuthorizeResponse is returned on successful authorization code creation. +type AuthorizeResponse struct { + Code string `json:"code"` + RedirectURI string `json:"redirect_uri"` + State string `json:"state"` +} + +// HandleAuthorize handles POST /oauth/authorize. +// It validates the OAuth parameters, creates an authorization code, and +// returns it as JSON. Authentication is handled by the token middleware. +func HandleAuthorize(c *echo.Context) error { + var req authorizeRequest + if err := c.Bind(&req); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, "Invalid request body") + } + + // Validate response_type + if req.ResponseType != "code" { + return echo.NewHTTPError(http.StatusBadRequest, "response_type must be 'code'") + } + + // Validate redirect_uri + if !ValidateRedirectURI(req.RedirectURI) { + return &models.ErrOAuthInvalidRedirectURI{} + } + + // Validate PKCE (required) + if req.CodeChallenge == "" || req.CodeChallengeMethod != "S256" { + return &models.ErrOAuthMissingPKCE{} + } + + // Get the authenticated user from the middleware + u, err := user.GetCurrentUser(c) + if err != nil { + return err + } + + s := db.NewSession() + defer s.Close() + + fullUser, err := user.GetUserByID(s, u.ID) + if err != nil { + _ = s.Rollback() + return err + } + + code, err := models.CreateOAuthCode(s, fullUser.ID, req.ClientID, req.RedirectURI, req.CodeChallenge, req.CodeChallengeMethod) + if err != nil { + _ = s.Rollback() + return err + } + + if err := s.Commit(); err != nil { + return err + } + + return c.JSON(http.StatusOK, AuthorizeResponse{ + Code: code, + RedirectURI: req.RedirectURI, + State: req.State, + }) +}