Skip to content

Commit

Permalink
feat: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
devgianlu committed Dec 22, 2023
0 parents commit fd735a3
Show file tree
Hide file tree
Showing 18 changed files with 453 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/.vscode
/.idea
*.iml
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# go-fileshare

A simple filesharing server in Go.
10 changes: 10 additions & 0 deletions auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package fileshare

import "errors"

var ErrAuthMalformed = errors.New("malformed authentication token")
var ErrAuthInvalid = errors.New("invalid authentication token")

type AuthProvider interface {
GetUser(jwt string) (*User, error)
}
46 changes: 46 additions & 0 deletions auth/jwt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package auth

import (
"fmt"
"github.com/devgianlu/go-fileshare"
"github.com/golang-jwt/jwt/v5"
)

type customClaims struct {
jwt.RegisteredClaims
Permissions []string `json:"permissions"`
}

type jwtAuthProvider struct {
secret []byte
parser *jwt.Parser
}

func (p *jwtAuthProvider) keyFunc(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %s", token.Header["alg"])
}

return p.secret, nil
}

func (p *jwtAuthProvider) GetUser(tokenString string) (*fileshare.User, error) {
token, err := p.parser.ParseWithClaims(tokenString, &customClaims{}, p.keyFunc)
if err != nil {
return nil, fileshare.NewError("", fileshare.ErrAuthMalformed, err)
}

if !token.Valid {
return nil, fileshare.NewError("", fileshare.ErrAuthInvalid, err)
}

claims := token.Claims.(*customClaims)
return &fileshare.User{Permissions: claims.Permissions}, nil
}

func NewJWTAuthProvider(secret []byte) fileshare.AuthProvider {
p := jwtAuthProvider{}
p.secret = secret
p.parser = jwt.NewParser()
return &p
}
28 changes: 28 additions & 0 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package main

import (
"github.com/devgianlu/go-fileshare"
"github.com/devgianlu/go-fileshare/auth"
"github.com/devgianlu/go-fileshare/http"
log "github.com/sirupsen/logrus"
)

type Server struct {
Auth fileshare.AuthProvider
HTTP fileshare.HttpServer
}

func main() {
const PORT = 8080 // FIXME
const SECRET = "test1234" // FIXME

log.SetLevel(log.TraceLevel)

s := Server{}
s.Auth = auth.NewJWTAuthProvider([]byte(SECRET))
s.HTTP = http.NewHTTPServer(PORT, s.Auth)

if err := s.HTTP.ListenForever(); err != nil {
log.WithError(err).Fatalf("failed listening")
}
}
31 changes: 31 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package fileshare

import (
"context"
"github.com/gofiber/fiber/v2"
)

type contextKey int

const (
userContextKey = contextKey(iota + 1)
)

func SetContextWithUser(ctx *fiber.Ctx, user *User) {
parent := ctx.UserContext()
if parent == nil {
parent = context.Background()
}

ctx.SetUserContext(context.WithValue(parent, userContextKey, user))
}

func UserFromContext(ctx *fiber.Ctx) *User {
parent := ctx.UserContext()
if parent == nil {
parent = context.Background()
}

user, _ := parent.Value(userContextKey).(*User)
return user
}
23 changes: 23 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package fileshare

import "fmt"

func NewError(message string, err error, more ...error) error {
var format string
var args []any
if message != "" {
format = "%w: %s"
args = []any{err, message}
} else {
format = "%w"
args = []any{err}
}

for _, e := range more {
format += ": %w"
args = append(args, e)
}

err = fmt.Errorf(format, args...)
return err
}
26 changes: 26 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module github.com/devgianlu/go-fileshare

go 1.21.3

require (
github.com/gofiber/fiber/v2 v2.51.0
github.com/gofiber/template/html/v2 v2.0.5
github.com/golang-jwt/jwt/v5 v5.2.0
github.com/sirupsen/logrus v1.9.3
)

require (
github.com/andybalholm/brotli v1.0.6 // indirect
github.com/gofiber/template v1.8.2 // indirect
github.com/gofiber/utils v1.1.0 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/klauspost/compress v1.17.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.50.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/sys v0.15.0 // indirect
)
50 changes: 50 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gofiber/fiber/v2 v2.51.0 h1:JNACcZy5e2tGApWB2QrRpenTWn0fq0hkFm6k0C86gKQ=
github.com/gofiber/fiber/v2 v2.51.0/go.mod h1:xaQRZQJGqnKOQnbQw+ltvku3/h8QxvNi8o6JiJ7Ll0U=
github.com/gofiber/template v1.8.2 h1:PIv9s/7Uq6m+Fm2MDNd20pAFFKt5wWs7ZBd8iV9pWwk=
github.com/gofiber/template v1.8.2/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8=
github.com/gofiber/template/html/v2 v2.0.5 h1:BKLJ6Qr940NjntbGmpO3zVa4nFNGDCi/IfUiDB9OC20=
github.com/gofiber/template/html/v2 v2.0.5/go.mod h1:RCF14eLeQDCSUPp0IGc2wbSSDv6yt+V54XB/+Unz+LM=
github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM=
github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0=
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M=
github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
21 changes: 21 additions & 0 deletions html/engine.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package html

import (
"embed"
"fmt"
"github.com/gofiber/template/html/v2"
"io/fs"
"net/http"
)

//go:embed templates/*.tmpl
var embeddedTemplatesFS embed.FS

func NewEngine() *html.Engine {
f, err := fs.Sub(embeddedTemplatesFS, "templates")
if err != nil {
panic(fmt.Sprintf("cannot load templates filesystem: %v", err))
}

return html.NewFileSystem(http.FS(f), ".tmpl")
}
5 changes: 5 additions & 0 deletions html/templates/index.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<html lang="en">
<body>
<!-- TODO -->
</body>
</html>
5 changes: 5 additions & 0 deletions http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package fileshare

type HttpServer interface {
ListenForever() error
}
50 changes: 50 additions & 0 deletions http/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package http

import (
"errors"
"fmt"
"github.com/devgianlu/go-fileshare"
"github.com/gofiber/fiber/v2"
"strings"
)

func (s *httpServer) getUser(authHeader string, authCookie string) (*fileshare.User, error) {
var token string
if len(authHeader) > 0 {
authParts := strings.Split(authHeader, " ")
if len(authParts) != 2 {
return nil, fmt.Errorf("invalid authorization header")
} else if authParts[0] != "Bearer" {
return nil, fmt.Errorf("unsupported authorization header: %s", authParts[0])
}

token = authParts[1]
} else if len(authCookie) > 0 {
token = authCookie
} else {
return nil, nil
}

user, err := s.auth.GetUser(token)
if errors.Is(err, fileshare.ErrAuthMalformed) {
return nil, newHttpError(fiber.StatusBadRequest, "malformed bearer token", err)
} else if errors.Is(err, fileshare.ErrAuthInvalid) {
return nil, newHttpError(fiber.StatusUnauthorized, "invalid bearer token", err)
} else if err != nil {
return nil, fmt.Errorf("failed authenticating: %w", err)
}

return user, nil
}

func (s *httpServer) newAuthHandler() fiber.Handler {
return func(ctx *fiber.Ctx) error {
if user, err := s.getUser(ctx.Get("Authorization"), ctx.Cookies("token")); err != nil {
return err
} else if user != nil {
fileshare.SetContextWithUser(ctx, user)
}

return ctx.Next()
}
}
50 changes: 50 additions & 0 deletions http/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package http

import (
"errors"
"github.com/gofiber/fiber/v2"
)

type httpError struct {
statusCode int
message string
err error
}

func (e *httpError) Error() string {
return e.err.Error()
}

func (e *httpError) Unwrap() error {
return e.err
}

func newHttpError(statusCode int, message string, err error) error {
return &httpError{statusCode, message, err}
}

func asHttpError(err error) (bool, int, string) {
var httpErr *httpError
if !errors.As(err, &httpErr) {
return false, 0, ""
}

return true, httpErr.statusCode, httpErr.message
}

func newErrorHandler() fiber.Handler {
return func(ctx *fiber.Ctx) error {
err := ctx.Next()
if ok, statusCode, message := asHttpError(err); ok {
// set status code and message header
ctx.Status(statusCode)
ctx.Set("X-Error-Message", message)

// return the error for the logger to see, we'll stop it in the error handler
return err
}

// unhandled error, let it propagate
return err
}
}
Loading

0 comments on commit fd735a3

Please sign in to comment.