Require login + api (#711)

Signed-off-by: Thomas Miceli <tho.miceli@gmail.com>
This commit is contained in:
Thomas
2026-06-08 03:28:49 +08:00
committed by GitHub
parent 31bc25e569
commit 34e5a16a26
3 changed files with 70 additions and 29 deletions
+4
View File
@@ -46,6 +46,10 @@ Authorization: Bearer og_xxxxxxxx
Each endpoint documents the scope it requires in its **Headers** section.
Note that every endpoint requires authentication when an admin enables the "Require login" setting, which both works for the API and the web interface.
The single gist endpoints are available without authentication when an admin enables "Allow individual gists without login" setting.
## Schema
+25
View File
@@ -244,6 +244,31 @@ func checkRequireLogin(next Handler) Handler {
return makeCheckRequireLogin(false)(next)
}
// makeApiCheckRequireLogin is the /api/v1 counterpart of makeCheckRequireLogin:
// it enforces the instance's RequireLogin / AllowGistsWithoutLogin settings on
// anonymous gist reads, but responds with a JSON 401 instead of redirecting to
// /login. ctx.User is already resolved from the Authorization header by
// apiBindAuth, so there is no token fallback to do here.
func makeApiCheckRequireLogin(isSingleGistAccess bool) Middleware {
return func(next Handler) Handler {
return func(ctx *context.Context) error {
if ctx.User != nil {
return next(ctx)
}
allow, err := auth.ShouldAllowUnauthenticatedGistAccess(handlers.ContextAuthInfo{Context: ctx}, isSingleGistAccess)
if err != nil {
return ctx.ErrorJson(500, "Failed to check if unauthenticated access is allowed", err)
}
if !allow {
return ctx.ErrorJson(401, "Requires authentication", nil)
}
return next(ctx)
}
}
}
func noRouteFound(ctx *context.Context) error {
return ctx.NotFound("Page not found")
}
+41 -29
View File
@@ -108,36 +108,48 @@ func (s *Server) registerRoutes() {
apiV1 := r.SubGroup("/api")
{
apiV1.Use(apiBindAuth)
apiV1.GET("/gists", apiv1.ListGists)
apiV1.POST("/gists", apiv1.CreateGist, apiRequireAuth, apiScope(db.ScopeGist, db.ReadWritePermission))
apiV1.GET("/gists/public", apiv1.ListPublicGists)
apiV1.GET("/gists/liked", apiv1.ListLikedGists, apiRequireAuth)
// /starred and /star are GitHub-compat aliases of the canonical
// /liked and /like routes (same handlers); openapi.yaml mentions them
// in a note but gives them no path entries of their own.
apiV1.GET("/gists/starred", apiv1.ListLikedGists, apiRequireAuth)
apiV1.GET("/gists/forked", apiv1.ListForkedGists, apiRequireAuth)
apiV1.GET("/gists/:uuid", apiv1.GetGist)
apiV1.PATCH("/gists/:uuid", apiv1.UpdateGist, apiRequireAuth, apiScope(db.ScopeGist, db.ReadWritePermission))
apiV1.DELETE("/gists/:uuid", apiv1.DeleteGist, apiRequireAuth, apiScope(db.ScopeGist, db.ReadWritePermission))
apiV1.GET("/gists/:uuid/commits", apiv1.ListCommits)
apiV1.GET("/gists/:uuid/:sha", apiv1.GetGistRevision)
apiV1.GET("/gists/:uuid/forks", apiv1.ListForks)
apiV1.POST("/gists/:uuid/forks", apiv1.ForkGist, apiRequireAuth, apiScope(db.ScopeGist, db.ReadWritePermission))
apiV1.GET("/gists/:uuid/like", apiv1.CheckLike, apiRequireAuth)
apiV1.GET("/gists/:uuid/star", apiv1.CheckLike, apiRequireAuth)
apiV1.PUT("/gists/:uuid/like", apiv1.ToggleLike, apiRequireAuth, apiScope(db.ScopeUser, db.ReadWritePermission))
apiV1.PUT("/gists/:uuid/star", apiv1.ToggleLike, apiRequireAuth, apiScope(db.ScopeUser, db.ReadWritePermission))
apiV1.GET("/gists/:uuid/files/:sha/:filename", apiv1.RawFile)
apiV1.GET("/user", apiv1.GetUser, apiRequireAuth, apiScope(db.ScopeUser, db.ReadPermission))
apiV1.PATCH("/user", apiv1.UpdateUser, apiRequireAuth, apiScope(db.ScopeUser, db.ReadWritePermission))
apiV1.GET("/user/:id", apiv1.GetUserByID)
apiV1.GET("/users/:username", apiv1.GetUserByUsername)
apiV1.GET("/users/:username/gists", apiv1.ListUserGists)
apiV1.GET("/users/:username/liked", apiv1.ListUserLikedGists)
apiV1.GET("/users/:username/starred", apiv1.ListUserLikedGists)
apiV1.GET("/users/:username/forked", apiv1.ListUserForkedGists)
// Single-gist reads: blocked when RequireLogin is set, unless
// AllowGistsWithoutLogin lets anonymous callers view individual gists.
single := apiV1.SubGroup("", makeApiCheckRequireLogin(true))
{
single.GET("/gists/:uuid", apiv1.GetGist)
single.PATCH("/gists/:uuid", apiv1.UpdateGist, apiRequireAuth, apiScope(db.ScopeGist, db.ReadWritePermission))
single.DELETE("/gists/:uuid", apiv1.DeleteGist, apiRequireAuth, apiScope(db.ScopeGist, db.ReadWritePermission))
single.GET("/gists/:uuid/commits", apiv1.ListCommits)
single.GET("/gists/:uuid/:sha", apiv1.GetGistRevision)
single.GET("/gists/:uuid/forks", apiv1.ListForks)
single.POST("/gists/:uuid/forks", apiv1.ForkGist, apiRequireAuth, apiScope(db.ScopeGist, db.ReadWritePermission))
single.GET("/gists/:uuid/like", apiv1.CheckLike, apiRequireAuth)
single.GET("/gists/:uuid/star", apiv1.CheckLike, apiRequireAuth)
single.PUT("/gists/:uuid/like", apiv1.ToggleLike, apiRequireAuth, apiScope(db.ScopeUser, db.ReadWritePermission))
single.PUT("/gists/:uuid/star", apiv1.ToggleLike, apiRequireAuth, apiScope(db.ScopeUser, db.ReadWritePermission))
single.GET("/gists/:uuid/files/:sha/:filename", apiv1.RawFile)
}
// List/aggregate reads: blocked entirely when RequireLogin is set.
multi := apiV1.SubGroup("", makeApiCheckRequireLogin(false))
{
multi.GET("/gists", apiv1.ListGists)
multi.POST("/gists", apiv1.CreateGist, apiRequireAuth, apiScope(db.ScopeGist, db.ReadWritePermission))
multi.GET("/gists/public", apiv1.ListPublicGists)
multi.GET("/gists/liked", apiv1.ListLikedGists, apiRequireAuth)
// /starred and /star are GitHub-compat aliases of the canonical
// /liked and /like routes (same handlers); openapi.yaml mentions them
// in a note but gives them no path entries of their own.
multi.GET("/gists/starred", apiv1.ListLikedGists, apiRequireAuth)
multi.GET("/gists/forked", apiv1.ListForkedGists, apiRequireAuth)
multi.GET("/user", apiv1.GetUser, apiRequireAuth, apiScope(db.ScopeUser, db.ReadPermission))
multi.PATCH("/user", apiv1.UpdateUser, apiRequireAuth, apiScope(db.ScopeUser, db.ReadWritePermission))
multi.GET("/user/:id", apiv1.GetUserByID)
multi.GET("/users/:username", apiv1.GetUserByUsername)
multi.GET("/users/:username/gists", apiv1.ListUserGists)
multi.GET("/users/:username/liked", apiv1.ListUserLikedGists)
multi.GET("/users/:username/starred", apiv1.ListUserLikedGists)
multi.GET("/users/:username/forked", apiv1.ListUserForkedGists)
}
apiV1.Any("", noRouteFoundApi)
}
r.GET("/api/openapi.yaml", api.OpenAPISpec)