Skip to content

Commit

Permalink
Added private visibility
Browse files Browse the repository at this point in the history
* Changed gist type and added HTML button on creation

* Adapted label and edit button

* Changed rules for git HTTP and SSH

* Adapt Readme features
  • Loading branch information
thomiceli committed Sep 2, 2023
1 parent 4f62388 commit 25316d7
Show file tree
Hide file tree
Showing 11 changed files with 114 additions and 33 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ A self-hosted pastebin **powered by Git**. [Try it here](https://opengist.thomic

## Features

* Create public or unlisted snippets
* Create public, unlisted or private snippets
* Clone / Pull / Push snippets **via Git** over HTTP or SSH
* Revisions history
* Syntax highlighting ; markdown & CSV support
Expand Down
14 changes: 7 additions & 7 deletions internal/models/gist.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type Gist struct {
Preview string
PreviewFilename string
Description string
Private bool
Private int // 0: public, 1: unlisted, 2: private
UserID uint
User User
NbFiles int
Expand Down Expand Up @@ -89,7 +89,7 @@ func GetAllGists(offset int) ([]*Gist, error) {
func GetAllGistsFromSearch(currentUserId uint, query string, offset int, sort string, order string) ([]*Gist, error) {
var gists []*Gist
err := db.Preload("User").Preload("Forked.User").
Where("((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", currentUserId).
Where("((gists.private = 0) or (gists.private > 0 and gists.user_id = ?))", currentUserId).
Where("gists.title like ? or gists.description like ?", "%"+query+"%", "%"+query+"%").
Limit(11).
Offset(offset * 10).
Expand All @@ -101,7 +101,7 @@ func GetAllGistsFromSearch(currentUserId uint, query string, offset int, sort st

func gistsFromUserStatement(fromUserId uint, currentUserId uint) *gorm.DB {
return db.Preload("User").Preload("Forked.User").
Where("((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", currentUserId).
Where("((gists.private = 0) or (gists.private > 0 and gists.user_id = ?))", currentUserId).
Where("users.id = ?", fromUserId).
Joins("join users on gists.user_id = users.id")
}
Expand All @@ -124,7 +124,7 @@ func CountAllGistsFromUser(fromUserId uint, currentUserId uint) (int64, error) {

func likedStatement(fromUserId uint, currentUserId uint) *gorm.DB {
return db.Preload("User").Preload("Forked.User").
Where("((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", currentUserId).
Where("((gists.private = 0) or (gists.private > 0 and gists.user_id = ?))", currentUserId).
Where("likes.user_id = ?", fromUserId).
Joins("join likes on gists.id = likes.gist_id").
Joins("join users on likes.user_id = users.id")
Expand All @@ -147,7 +147,7 @@ func CountAllGistsLikedByUser(fromUserId uint, currentUserId uint) (int64, error

func forkedStatement(fromUserId uint, currentUserId uint) *gorm.DB {
return db.Preload("User").Preload("Forked.User").
Where("gists.forked_id is not null and ((gists.private = 0) or (gists.private = 1 and gists.user_id = ?))", currentUserId).
Where("gists.forked_id is not null and ((gists.private = 0) or (gists.private > 0 and gists.user_id = ?))", currentUserId).
Where("gists.user_id = ?", fromUserId).
Joins("join users on gists.user_id = users.id")
}
Expand Down Expand Up @@ -243,7 +243,7 @@ func (gist *Gist) GetForks(currentUserId uint, offset int) ([]*Gist, error) {
var gists []*Gist
err := db.Model(&gist).Preload("User").
Where("forked_id = ?", gist.ID).
Where("(gists.private = 0) or (gists.private = 1 and gists.user_id = ?)", currentUserId).
Where("(gists.private = 0) or (gists.private > 0 and gists.user_id = ?)", currentUserId).
Limit(11).
Offset(offset * 10).
Order("updated_at desc").
Expand Down Expand Up @@ -379,7 +379,7 @@ func (gist *Gist) UpdatePreviewAndCount() error {
type GistDTO struct {
Title string `validate:"max=50" form:"title"`
Description string `validate:"max=150" form:"description"`
Private bool `form:"private"`
Private int `validate:"number,min=0,max=2" form:"private"`
Files []FileDTO `validate:"min=1,dive"`
}

Expand Down
13 changes: 11 additions & 2 deletions internal/ssh/git_ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,21 @@ func runGitCommand(ch ssh.Channel, gitCmd string, key string, ip string) error {
return errors.New("internal server error")
}

if verb == "receive-pack" || requireLogin == "1" {
// Check for the key if :
// - user wants to push the gist
// - user wants to clone a private gist
// - gist is not found (obfuscation)
// - admin setting to require login is set to true
if verb == "receive-pack" ||
gist.Private == 2 ||
gist.ID == 0 ||
requireLogin == "1" {

pubKey, err := models.SSHKeyExistsForUser(key, gist.UserID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
log.Warn().Msg("Invalid SSH authentication attempt from " + ip)
return errors.New("unauthorized")
return errors.New("gist not found")
}
errorSsh("Failed to get user by SSH key id", err)
return errors.New("internal server error")
Expand Down
20 changes: 18 additions & 2 deletions internal/web/gist.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,30 @@ func gistInit(next echo.HandlerFunc) echo.HandlerFunc {
setData(ctx, "hasLiked", hasLiked)
}

if gist.Private {
if gist.Private > 0 {
setData(ctx, "NoIndex", true)
}

return next(ctx)
}
}

// gistSoftInit try to load a gist (same as gistInit) but does not return a 404 if the gist is not found
// useful for git clients using HTTP to obfuscate the existence of a private gist
func gistSoftInit(next echo.HandlerFunc) echo.HandlerFunc {
return func(ctx echo.Context) error {
userName := ctx.Param("user")
gistName := ctx.Param("gistname")

gistName = strings.TrimSuffix(gistName, ".git")

gist, _ := models.GetGist(userName, gistName)
setData(ctx, "gist", gist)

return next(ctx)
}
}

func allGists(ctx echo.Context) error {
var err error
var urlPage string
Expand Down Expand Up @@ -400,7 +416,7 @@ func processCreate(ctx echo.Context) error {
func toggleVisibility(ctx echo.Context) error {
var gist = getData(ctx, "gist").(*models.Gist)

gist.Private = !gist.Private
gist.Private = (gist.Private + 1) % 3
if err := gist.Update(); err != nil {
return errorRes(500, "Error updating this gist", err)
}
Expand Down
15 changes: 13 additions & 2 deletions internal/web/git_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,23 @@ func gitHttp(ctx echo.Context) error {

gist := getData(ctx, "gist").(*models.Gist)

// Shows basic auth if :
// - user wants to push the gist
// - user wants to clone a private gist
// - gist is not found (obfuscation)
// - admin setting to require login is set to true
noAuth := (ctx.QueryParam("service") == "git-upload-pack" ||
strings.HasSuffix(ctx.Request().URL.Path, "git-upload-pack") ||
ctx.Request().Method == "GET") &&
gist.Private != 2 &&
gist.ID != 0 &&
!getData(ctx, "RequireLogin").(bool)

repositoryPath := git.RepositoryPath(gist.User.Username, gist.Uuid)

if _, err := os.Stat(repositoryPath); os.IsNotExist(err) {
if err != nil {
return errorRes(500, "Repository does not exist", err)
return errorRes(404, "Repository directory does not exist", err)
}
}

Expand All @@ -82,12 +89,16 @@ func gitHttp(ctx echo.Context) error {
return basicAuth(ctx)
}

if gist.ID == 0 {
return errorRes(404, "Not found", nil)
}

if ok, err := argon2id.verify(authPassword, gist.User.Password); !ok || gist.User.Username != authUsername {
if err != nil {
return errorRes(500, "Cannot verify password", err)
}
log.Warn().Msg("Invalid HTTP authentication attempt from " + ctx.RealIP())
return errorRes(403, "Unauthorized", nil)
return errorRes(404, "Not found", nil)
}

return route.handler(ctx)
Expand Down
22 changes: 18 additions & 4 deletions internal/web/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ var re = regexp.MustCompile("[^a-z0-9]+")
var fm = template.FuncMap{
"split": strings.Split,
"indexByte": strings.IndexByte,
"toInt": func(i string) int64 {
val, _ := strconv.ParseInt(i, 10, 64)
"toInt": func(i string) int {
val, _ := strconv.Atoi(i)
return val
},
"inc": func(i int64) int64 {
"inc": func(i int) int {
return i + 1
},
"splitGit": func(i string) []string {
Expand Down Expand Up @@ -88,6 +88,20 @@ var fm = template.FuncMap{
return config.C.ExternalUrl + "/" + manifestEntries[jsfile].File
},
"defaultAvatar": defaultAvatar,
"visibilityStr": func(visibility int, lowercase bool) string {
s := "Public"
switch visibility {
case 1:
s = "Unlisted"
case 2:
s = "Private"
}

if lowercase {
return strings.ToLower(s)
}
return s
},
}

var EmbedFS fs.FS
Expand Down Expand Up @@ -226,7 +240,7 @@ func Start() {
debugStr := ""
// Git HTTP routes
if config.C.HttpGit {
e.Any("/:user/:gistname/*", gitHttp, gistInit)
e.Any("/:user/:gistname/*", gitHttp, gistSoftInit)
debugStr = " (with Git over HTTP)"
}

Expand Down
16 changes: 16 additions & 0 deletions public/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,20 @@ document.addEventListener('DOMContentLoaded', () => {
});
};
});

const gistmenuvisibility = document.getElementById('gist-menu-visibility');
if (gistmenuvisibility) {
let submitgistbutton = (document.getElementById('submit-gist') as HTMLInputElement);
document.getElementById('gist-visibility-menu-button')!.onclick = () => {
console.log("z");
gistmenuvisibility!.classList.toggle('hidden');
}
Array.from(document.querySelectorAll('.gist-visibility-option')).forEach((el) => {
(el as HTMLElement).onclick = () => {
submitgistbutton.textContent = "Create " + el.textContent.toLowerCase() + " gist";
submitgistbutton!.value = (el as HTMLElement).dataset.visibility || '0';
gistmenuvisibility!.classList.add('hidden');
}
});
}
});
3 changes: 1 addition & 2 deletions templates/base/gist_header.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,7 @@ <h1 class="text-2xl font-bold leading-tight break-all">
<p class="mt-1 max-w-2xl text-sm text-slate-500">Forked from <a href="{{ $.c.ExternalUrl }}/{{ .gist.Forked.User.Username }}/{{ .gist.Forked.Uuid }}">{{ .gist.Forked.User.Username }}/{{ .gist.Forked.Title }}</a></p>
{{ end }}
<p class="mt-1 max-w-2xl text-sm text-slate-500">Last active <span class="moment-timestamp"> {{ .gist.UpdatedAt }} </span>
{{ if .gist.Private }} • <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300"> Unlisted </span>{{ end }}

{{ if .gist.Private }} • <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300"> {{ visibilityStr .gist.Private false }} </span>{{ end }}
</p>
<p class="mt-3 max-w-2xl text-slate-700 dark:text-slate-300">{{ .gist.Description }}</p>
</header>
Expand Down
2 changes: 1 addition & 1 deletion templates/pages/all.html
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ <h4 class="text-md leading-tight break-all py-1 flex-auto">
</div>
<h5 class="text-sm text-slate-500 pb-1">Last active <span class="moment-timestamp">{{ $gist.UpdatedAt }}</span>
{{ if $gist.Forked }} • Forked from <a href="{{ $.c.ExternalUrl }}/{{ $gist.Forked.User.Username }}/{{ $gist.Forked.Uuid }}">{{ $gist.Forked.User.Username }}/{{ $gist.Forked.Title }}</a> {{ end }}
{{ if $gist.Private }} • <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300"> Unlisted </span>{{ end }}</h5>
{{ if $gist.Private }} • <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-slate-700 dark:text-slate-300"> {{ visibilityStr $gist.Private false }} </span>{{ end }}</h5>
<h5 class="text-xs text-slate-700 dark:text-slate-300 py-1">{{ $gist.Description }}</h5>
<a href="{{ $.c.ExternalUrl }}/{{ $gist.User.Username }}/{{ $gist.Uuid }}" class="text-slate-700 dark:text-slate-300">
<div class="rounded-md border border-1 border-gray-200 dark:border-gray-700 overflow-auto hover:border-primary-600">
Expand Down
21 changes: 19 additions & 2 deletions templates/pages/create.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,25 @@ <h1 class="text-2xl font-bold leading-tight text-slate-700 dark:text-slate-300">

<div class="flex">
<button type="button" id="add-file" class="inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-gray-700 dark:text-white bg-gray-100 dark:bg-gray-600 hover:bg-gray-200 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">Add file</button>
<button type="submit" name="private" value="1" class="ml-auto inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-gray-700 dark:text-white bg-gray-100 dark:bg-gray-600 hover:bg-gray-200 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500">Create unlisted gist</button>
<button type="submit" name="private" value="0" class="ml-2 inline-flex items-center px-4 py-2 border border-transparent border-gray-200 dark:border-gray-700 text-sm font-medium rounded-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">Create public gist</button>

<div class="ml-auto inline-flex ">
<button id="submit-gist" type="submit" name="private" value="0" class="ml-2 items-center px-4 py-2 border border-transparent border-primary-200 dark:border-primary-700 text-sm font-medium rounded-l-md shadow-sm text-white dark:text-white bg-primary-500 hover:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 z-20">Create public gist</button>
<div class="relative -ml-px block">
<button type="button" class="relative inline-flex items-center rounded-r-md bg-primary-500 hover:bg-primary-600 px-2 py-2 text-gray-400 border border-transparent border-primary-200 dark:border-primary-700 focus:z-10" id="gist-visibility-menu-button">
<span class="sr-only">Open options</span>
<svg class="h-5 w-5" viewBox="0 0 20 20" fill="white" aria-hidden="true">
<path fill-rule="evenodd" d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z" clip-rule="evenodd" />
</svg>
</button>
<div id="gist-menu-visibility" class="hidden absolute right-0 z-10 mt-2 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="gist-visibility-menu-button">
<div class="rounded-md dark:bg-gray-800 bg-white shadow-lg ring-1 ring-gray-50 dark:ring-gray-700 focus:outline-none" role="none">
<span class="text-gray-700 block px-4 py-2 text-sm cursor-pointer dark:text-slate-300 hover:text-slate-500 dark:hover:text-slate-400 gist-visibility-option" data-visibility="0" role="menuitem">Public</span>
<span class="text-gray-700 block px-4 py-2 text-sm cursor-pointer dark:text-slate-300 hover:text-slate-500 dark:hover:text-slate-400 gist-visibility-option" data-visibility="1" role="menuitem">Unlisted</span>
<span class="text-gray-700 block px-4 py-2 text-sm cursor-pointer dark:text-slate-300 hover:text-slate-500 dark:hover:text-slate-400 gist-visibility-option" data-visibility="2" role="menuitem">Private</span>
</div>
</div>
</div>
</div>
</div>
{{ .csrfHtml }}
</form>
Expand Down
19 changes: 9 additions & 10 deletions templates/pages/edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,17 @@ <h1 class="text-2xl font-bold leading-tight text-slate-700 dark:text-slate-300">
<form id="visibility" class="flex items-center whitespace-nowrap" method="post" action="/{{ .gist.User.Username }}/{{ .gist.Uuid }}/visibility">
{{ .csrfHtml }}
<button type="submit" class="ml-auto relative inline-flex items-center space-x-2 rounded-md border border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 px-2 py-1.5 text-xs font-medium text-slate-700 dark:text-slate-300 hover:bg-gray-100 dark:hover:bg-gray-700 hover:border-gray-500 hover:text-slate-700 dark:hover:text-slate-300 focus:border-primary-500 focus:outline-none focus:ring-1 focus:ring-primary-500 leading-3">
{{ if .gist.Private }}
<svg xmlns="http:https://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
Make public
{{ if eq .gist.Private 2 }}
<svg xmlns="http:https://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
{{ else }}
<svg xmlns="http:https://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
</svg>
Make unlisted
<svg xmlns="http:https://www.w3.org/2000/svg" class="h-4 w-4 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />
</svg>
{{ end }}
Make {{ visibilityStr (inc .gist.Private) true }}
</button>
</form>
<form id="delete" onsubmit="return confirm('Are you sure you want to delete this gist ?')" class="ml-2 flex items-center" method="post" action="/{{ .gist.User.Username }}/{{ .gist.Uuid }}/delete">
Expand Down

0 comments on commit 25316d7

Please sign in to comment.