feat: open file original in a new tab via authenticated direct link
The file viewer's preview is now a real link (target=_blank) to the original, instead of fetching it into a blob. A navigation can't send the auth header, so the access token rides in the query — the auth middleware accepts ?access_token= as a fallback, but only for GET, so a crafted link can't drive a mutation. GetContent gains an ?inline=1 toggle (Content-Disposition: inline) so the tab views the original instead of downloading it; download stays the default. Documented in openapi.yaml; TestMediaQueryTokenAuth covers GET-with-query-token (200), missing token (401) and query-token rejected on a non-GET (401). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -24,8 +24,8 @@ func NewAuthMiddleware(authSvc *service.AuthService) *AuthMiddleware {
|
||||
// On success it calls c.Next(); on failure it aborts with 401 JSON.
|
||||
func (m *AuthMiddleware) Handle() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
raw := c.GetHeader("Authorization")
|
||||
if !strings.HasPrefix(raw, "Bearer ") {
|
||||
token := bearerToken(c)
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, errorBody{
|
||||
Code: domain.ErrUnauthorized.Code(),
|
||||
Message: "authorization header missing or malformed",
|
||||
@@ -33,7 +33,6 @@ func (m *AuthMiddleware) Handle() gin.HandlerFunc {
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
token := strings.TrimPrefix(raw, "Bearer ")
|
||||
|
||||
claims, err := m.authSvc.ValidateAccessToken(c.Request.Context(), token)
|
||||
if err != nil {
|
||||
@@ -50,3 +49,18 @@ func (m *AuthMiddleware) Handle() gin.HandlerFunc {
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// bearerToken extracts the access token from the Authorization header. As a
|
||||
// fallback it accepts an ?access_token= query parameter, but only for GET
|
||||
// requests — this lets the browser open media (e.g. /files/{id}/content) via a
|
||||
// plain link/new tab, where it can't send the header, without allowing a crafted
|
||||
// link to drive a state-changing request.
|
||||
func bearerToken(c *gin.Context) string {
|
||||
if raw := c.GetHeader("Authorization"); strings.HasPrefix(raw, "Bearer ") {
|
||||
return strings.TrimPrefix(raw, "Bearer ")
|
||||
}
|
||||
if c.Request.Method == http.MethodGet {
|
||||
return c.Query("access_token")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user