Merge pull request #19 from thomiceli/feature/all-private

Restrict/unrestrict gists visibility to anonymous users
This commit is contained in:
Thomas Miceli 2023-04-29 20:22:34 +02:00 committed by GitHub
commit 5ba90af04c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 77 additions and 23 deletions

View file

@ -31,6 +31,7 @@ A self-hosted pastebin **powered by Git**. [Try it here](https://opengist.thomic
* Avatars * Avatars
* Responsive UI * Responsive UI
* Enable or disable signups * Enable or disable signups
* Restrict or unrestrict snippets visibility to anonymous users
* Admin panel : delete users/gists; clean database/filesystem by syncing gists * Admin panel : delete users/gists; clean database/filesystem by syncing gists
* SQLite database * SQLite database
* Logging * Logging

View file

@ -11,6 +11,7 @@ type AdminSetting struct {
const ( const (
SettingDisableSignup = "disable-signup" SettingDisableSignup = "disable-signup"
SettingRequireLogin = "require-login"
) )
func GetSetting(key string) (string, error) { func GetSetting(key string) (string, error) {
@ -19,9 +20,24 @@ func GetSetting(key string) (string, error) {
return setting.Value, err return setting.Value, err
} }
func GetSettings() (map[string]string, error) {
var settings []AdminSetting
err := db.Find(&settings).Error
if err != nil {
return nil, err
}
result := make(map[string]string)
for _, setting := range settings {
result[setting.Key] = setting.Value
}
return result, nil
}
func UpdateSetting(key string, value string) error { func UpdateSetting(key string, value string) error {
return db.Clauses(clause.OnConflict{ return db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "key"}}, // key colume Columns: []clause.Column{{Name: "key"}}, // key column
DoUpdates: clause.AssignmentColumns([]string{"value"}), DoUpdates: clause.AssignmentColumns([]string{"value"}),
}).Create(&AdminSetting{ }).Create(&AdminSetting{
Key: key, Key: key,

View file

@ -28,6 +28,7 @@ func Setup(dbpath string) error {
// Default admin setting values // Default admin setting values
return initAdminSettings(map[string]string{ return initAdminSettings(map[string]string{
SettingDisableSignup: "0", SettingDisableSignup: "0",
SettingRequireLogin: "0",
}) })
} }

View file

@ -37,7 +37,12 @@ func runGitCommand(ch ssh.Channel, gitCmd string, keyID uint, ip string) error {
return errors.New("gist not found") return errors.New("gist not found")
} }
if verb == "receive-pack" { requireLogin, err := models.GetSetting(models.SettingRequireLogin)
if err != nil {
return errors.New("internal server error")
}
if verb == "receive-pack" || requireLogin == "1" {
user, err := models.GetUserBySSHKeyID(keyID) user, err := models.GetUserBySSHKeyID(keyID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {

View file

@ -30,7 +30,7 @@ func register(ctx echo.Context) error {
} }
func processRegister(ctx echo.Context) error { func processRegister(ctx echo.Context) error {
if getData(ctx, "signupDisabled") == true { if getData(ctx, "DisableSignup") == true {
return errorRes(403, "Signing up is disabled", nil) return errorRes(403, "Signing up is disabled", nil)
} }
@ -148,6 +148,10 @@ func oauthCallback(ctx echo.Context) error {
// if user is not in database, create it // if user is not in database, create it
userDB, err := models.GetUserByProvider(user.UserID, user.Provider) userDB, err := models.GetUserByProvider(user.UserID, user.Provider)
if err != nil { if err != nil {
if getData(ctx, "DisableSignup") == true {
return errorRes(403, "Signing up is disabled", nil)
}
if !errors.Is(err, gorm.ErrRecordNotFound) { if !errors.Is(err, gorm.ErrRecordNotFound) {
return errorRes(500, "Cannot get user", err) return errorRes(500, "Cannot get user", err)
} }
@ -166,10 +170,6 @@ func oauthCallback(ctx echo.Context) error {
} }
if err = userDB.Create(); err != nil { if err = userDB.Create(); err != nil {
if getData(ctx, "signupDisabled") == true {
return errorRes(403, "Signing up is disabled", nil)
}
if models.IsUniqueConstraintViolation(err) { if models.IsUniqueConstraintViolation(err) {
addFlash(ctx, "Username "+user.NickName+" already exists in Opengist", "error") addFlash(ctx, "Username "+user.NickName+" already exists in Opengist", "error")
return redirect(ctx, "/login") return redirect(ctx, "/login")
@ -281,7 +281,7 @@ func oauth(ctx echo.Context) error {
} }
} }
ctxValue := context.WithValue(ctx.Request().Context(), providerKey, provider) ctxValue := context.WithValue(ctx.Request().Context(), gothic.ProviderParamKey, provider)
ctx.SetRequest(ctx.Request().WithContext(ctxValue)) ctx.SetRequest(ctx.Request().WithContext(ctxValue))
if provider != "github" && provider != "gitea" { if provider != "github" && provider != "gitea" {
return errorRes(400, "Unsupported provider", nil) return errorRes(400, "Unsupported provider", nil)

View file

@ -47,9 +47,10 @@ func gitHttp(ctx echo.Context) error {
gist := getData(ctx, "gist").(*models.Gist) gist := getData(ctx, "gist").(*models.Gist)
noAuth := ctx.QueryParam("service") == "git-upload-pack" || noAuth := (ctx.QueryParam("service") == "git-upload-pack" ||
strings.HasSuffix(ctx.Request().URL.Path, "git-upload-pack") || strings.HasSuffix(ctx.Request().URL.Path, "git-upload-pack") ||
ctx.Request().Method == "GET" ctx.Request().Method == "GET") &&
!getData(ctx, "RequireLogin").(bool)
repositoryPath := git.RepositoryPath(gist.User.Username, gist.Uuid) repositoryPath := git.RepositoryPath(gist.User.Username, gist.Uuid)

View file

@ -191,12 +191,12 @@ func Start() {
g2.PUT("/set-setting", adminSetSetting) g2.PUT("/set-setting", adminSetSetting)
} }
g1.GET("/all", allGists) g1.GET("/all", allGists, checkRequireLogin)
g1.GET("/:user", allGists) g1.GET("/:user", allGists, checkRequireLogin)
g3 := g1.Group("/:user/:gistname") g3 := g1.Group("/:user/:gistname")
{ {
g3.Use(gistInit) g3.Use(checkRequireLogin, gistInit)
g3.GET("", gistIndex) g3.GET("", gistIndex)
g3.GET("/rev/:revision", gistIndex) g3.GET("/rev/:revision", gistIndex)
g3.GET("/revisions", revisions) g3.GET("/revisions", revisions)
@ -243,11 +243,9 @@ func dataInit(next echo.HandlerFunc) echo.HandlerFunc {
ctx.SetRequest(ctx.Request().WithContext(ctxValue)) ctx.SetRequest(ctx.Request().WithContext(ctxValue))
setData(ctx, "loadStartTime", time.Now()) setData(ctx, "loadStartTime", time.Now())
disableSignup, err := models.GetSetting(models.SettingDisableSignup) if err := loadSettings(ctx); err != nil {
if err != nil { return errorRes(500, "Cannot read settings from database", err)
return errorRes(500, "Cannot read setting from database", err)
} }
setData(ctx, "signupDisabled", disableSignup == "1")
setData(ctx, "githubOauth", config.C.GithubClientKey != "" && config.C.GithubSecret != "") setData(ctx, "githubOauth", config.C.GithubClientKey != "" && config.C.GithubSecret != "")
setData(ctx, "giteaOauth", config.C.GiteaClientKey != "" && config.C.GiteaSecret != "") setData(ctx, "giteaOauth", config.C.GiteaClientKey != "" && config.C.GiteaSecret != "")
@ -318,6 +316,21 @@ func logged(next echo.HandlerFunc) echo.HandlerFunc {
} }
} }
func checkRequireLogin(next echo.HandlerFunc) echo.HandlerFunc {
return func(ctx echo.Context) error {
if user := getUserLogged(ctx); user != nil {
return next(ctx)
}
require := getData(ctx, "RequireLogin")
if require == true {
addFlash(ctx, "You must be logged in to access gists", "error")
return redirect(ctx, "/login")
}
return next(ctx)
}
}
func cacheControl(next echo.HandlerFunc) echo.HandlerFunc { func cacheControl(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error { return func(c echo.Context) error {
c.Response().Header().Set(echo.HeaderCacheControl, "public, max-age=31536000") c.Response().Header().Set(echo.HeaderCacheControl, "public, max-age=31536000")

View file

@ -18,10 +18,8 @@ import (
"strings" "strings"
) )
type providerKeyType string
type dataTypeKey string type dataTypeKey string
const providerKey providerKeyType = "provider"
const dataKey dataTypeKey = "data" const dataKey dataTypeKey = "data"
func setData(ctx echo.Context, key string, value any) { func setData(ctx echo.Context, key string, value any) {
@ -110,6 +108,20 @@ func deleteCsrfCookie(ctx echo.Context) {
ctx.SetCookie(&http.Cookie{Name: "_csrf", Path: "/", MaxAge: -1}) ctx.SetCookie(&http.Cookie{Name: "_csrf", Path: "/", MaxAge: -1})
} }
func loadSettings(ctx echo.Context) error {
settings, err := models.GetSettings()
if err != nil {
return err
}
for key, value := range settings {
s := strings.ReplaceAll(key, "-", " ")
s = title.String(s)
setData(ctx, strings.ReplaceAll(s, " ", ""), value == "1")
}
return nil
}
type OpengistValidator struct { type OpengistValidator struct {
v *validator.Validate v *validator.Validate
} }

View file

@ -1,5 +1,6 @@
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
registerDomSetting(document.getElementById('disable-signup') as HTMLInputElement); registerDomSetting(document.getElementById('disable-signup') as HTMLInputElement);
registerDomSetting(document.getElementById('require-login') as HTMLInputElement);
}); });
const setSetting = (key: string, value: string) => { const setSetting = (key: string, value: string) => {

View file

@ -66,7 +66,7 @@
</svg> </svg>
</a> </a>
{{ else }} {{ else }}
{{ if not .signupDisabled }} {{ if not .DisableSignup }}
<a href="/register" class="inline-flex text-slate-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium"> <a href="/register" class="inline-flex text-slate-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium">
<p class="text-slate-300 mr-1">Register</p> <p class="text-slate-300 mr-1">Register</p>
</a> </a>

View file

@ -81,7 +81,11 @@
<div class="space-y-2"> <div class="space-y-2">
<div> <div>
<label for="disable-signup" class="text-sm text-slate-300">Disable signup</label> <label for="disable-signup" class="text-sm text-slate-300">Disable signup</label>
<input type="checkbox" id="disable-signup" name="disable-signup" {{ if .signupDisabled }}checked="checked"{{ end }} class="ml-1 h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-600" /> <input type="checkbox" id="disable-signup" name="disable-signup" {{ if .DisableSignup }}checked="checked"{{ end }} class="ml-1 h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-600" />
</div>
<div>
<label for="disable-signup" class="text-sm text-slate-300">Login required</label>
<input type="checkbox" id="require-login" name="require-login" {{ if .RequireLogin }}checked="checked"{{ end }} class="ml-1 h-4 w-4 rounded border-gray-300 text-primary-600 focus:ring-primary-600" />
</div> </div>
</div> </div>
</div> </div>

View file

@ -8,7 +8,7 @@
</header> </header>
<main class="mt-4"> <main class="mt-4">
{{ if and .signupDisabled (ne .title "Login") }} {{ if and .DisableSignup (ne .title "Login") }}
<p class="italic">Administrator has disabled signing up</p> <p class="italic">Administrator has disabled signing up</p>
{{ else }} {{ else }}
<div class="sm:col-span-6"> <div class="sm:col-span-6">
@ -33,7 +33,7 @@
<div class="flex-auto"> <div class="flex-auto">
<button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-700 text-sm font-medium rounded-md shadow-sm text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">Login</button> <button type="submit" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-700 text-sm font-medium rounded-md shadow-sm text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">Login</button>
</div> </div>
{{ if not .signupDisabled }} {{ if not .DisableSignup }}
<span class="float-right text-sm py-2 underline"><a href="/register">Register instead →</a></span> <span class="float-right text-sm py-2 underline"><a href="/register">Register instead →</a></span>
{{ end }} {{ end }}
</div> </div>