Skip to content

Commit

Permalink
feat: basic auth functions
Browse files Browse the repository at this point in the history
  • Loading branch information
vaayne committed Jul 16, 2024
1 parent eb47c91 commit ade5aef
Show file tree
Hide file tree
Showing 14 changed files with 235 additions and 51 deletions.
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ go 1.22.4

require (
github.com/caarlos0/env/v11 v11.1.0
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.6.0
github.com/joho/godotenv v1.5.1
github.com/labstack/echo/v4 v4.12.0
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/riverqueue/river v0.9.0
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.9.0
golang.org/x/oauth2 v0.21.0
gopkg.in/telebot.v3 v3.3.6
)

require (
Expand All @@ -31,5 +34,4 @@ require (
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.5.0 // indirect
gopkg.in/telebot.v3 v3.3.6 // indirect
)
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
Expand Down Expand Up @@ -185,6 +187,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
Expand Down Expand Up @@ -542,6 +546,8 @@ golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down
51 changes: 51 additions & 0 deletions internal/pkg/auth/jwt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package auth

import (
"fmt"
"time"
"vibrain/internal/pkg/config"

"github.com/golang-jwt/jwt/v5"
)

var signingMethod = jwt.SigningMethodHS256

type JwtUser struct {
UserID string `json:"user_id"`
OAuthProvider string `json:"oauth_provider,omitempty"`
AccessToken string `json:"access_token"`
TokenType string `json:"token_type,omitempty"`
Expiry time.Time `json:"expiry,omitempty"`
}

func getJWTSecret() []byte {
return []byte(config.Settings.JWTSecret)
}

func GenerateJWT(user JwtUser) (string, error) {
token := jwt.NewWithClaims(signingMethod, jwt.MapClaims{
"exp": time.Now().Add(time.Hour * 24).Unix(),
"user": user,
})
return token.SignedString(getJWTSecret())
}

func ValidateJWT(tokenString string) (*JwtUser, error) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return getJWTSecret(), nil
})
if err != nil {
return nil, fmt.Errorf("invalid jwt token: %w", err)
}

claim := token.Claims.(jwt.MapClaims)
user, ok := claim["user"]
if !ok {
return nil, fmt.Errorf("user claim not found")
}

return user.(*JwtUser), nil
}
46 changes: 46 additions & 0 deletions internal/pkg/auth/oauth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package auth

import (
"context"
"fmt"
"strings"
"vibrain/internal/pkg/config"

"golang.org/x/oauth2"
"golang.org/x/oauth2/github"
)

func getOAuth2Config(provider string) (*oauth2.Config, error) {
if strings.ToLower(provider) == "github" {
return &oauth2.Config{
ClientID: config.Settings.OAuthGithubKey,
ClientSecret: config.Settings.OAuthGithubSecret,
Endpoint: github.Endpoint,
RedirectURL: fmt.Sprintf("%s/oauth/github/callback", config.Settings.Fqdn),
Scopes: []string{"user:email"},
}, nil
}

return nil, fmt.Errorf("oauth provider '%s' not found", provider)
}

func GetOAuth2RedirectURL(ctx context.Context, provider string) (string, error) {
cfg, err := getOAuth2Config(provider)
if err != nil {
return "", fmt.Errorf("failed to get oauth config: %w", err)
}
authCodeUrl := cfg.AuthCodeURL("state:"+provider, oauth2.AccessTypeOnline)
return authCodeUrl, nil
}

func GetOAuth2Token(ctx context.Context, provider, code string) (*oauth2.Token, error) {
cfg, err := getOAuth2Config(provider)
if err != nil {
return nil, fmt.Errorf("failed to get oauth config: %w", err)
}
token, err := cfg.Exchange(ctx, code)
if err != nil {
return nil, fmt.Errorf("failed to exchange oauth code: %w", err)
}
return token, nil
}
32 changes: 8 additions & 24 deletions internal/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,19 @@ import (

var Settings = &Config{}

// type DatabaseConfig struct {
// Driver string `json:"driver"` // postgres, mysql, sqlite3
// Host string `json:"host"`
// Port int `json:"port"`
// User string `json:"user"`
// Password string `json:"password"`
// Name string `json:"name"`
// }

// func (d DatabaseConfig) DSN() string {
// switch d.Driver {
// case "postgres":
// return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", d.Host, d.Port, d.User, d.Password, d.Name)
// case "mysql":
// return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", d.User, d.Password, d.Host, d.Port, d.Name)
// case "sqlite3":
// return d.Name
// }
// return ""
// }

type Config struct {
Debug bool `env:"DEBUG" envDefault:"false"`
Port int `env:"PORT" envDefault:"1323"`
Debug bool `env:"DEBUG" envDefault:"false"`
Fqdn string `env:"FQDN" envDefault:"localhost:1323"`
Port int `env:"PORT" envDefault:"1323"`

DatabaseURL string `env:"DATABASE_URL,required"`
QueueDatabaseURL string `env:"QUEUE_DATABASE_URL,expand" envDefault:"${DATABASE_URL}"`

TelegramToken string `env:"TELEGRAM_TOKEN,required"`
TelegramToken string `env:"TELEGRAM_TOKEN"`

JWTSecret string `env:"JWT_SECRET,required"`
OAuthGithubSecret string `env:"OAUTH_GITHUB_SECRET,required"`
OAuthGithubKey string `env:"OAUTH_GITHUB_KEY,required"`
}

func init() {
Expand Down
4 changes: 2 additions & 2 deletions internal/pkg/constant/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package constant
type ContextKey string

const (
ContextKeyContext = "ctx"
ContextKeyContext = "ctx"
ContextKeyRequestID = "request_id"
ContextKeyLogger = "logger"
ContextKeyUserID = "user_id"
ContextKeyUserName = "user_name"
ContextKeyUserName = "user_name"
)
1 change: 0 additions & 1 deletion internal/port/bots/bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ func New(token string, handlers ...Handler) (*tele.Bot, error) {
Token: token,
Poller: &tele.LongPoller{Timeout: 10 * time.Second},
})

if err != nil {
return nil, fmt.Errorf("failed to create new bot: %w", err)
}
Expand Down
2 changes: 0 additions & 2 deletions internal/port/bots/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ import (
tele "gopkg.in/telebot.v3"
)


type Service struct {
b *tele.Bot
}


func NewServer(token string, handlers ...Handler) (*Service, error) {
b, err := New(token, handlers...)
if err != nil {
Expand Down
63 changes: 63 additions & 0 deletions internal/port/httpserver/handlers/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package handlers

import (
"fmt"
"net/http"
"vibrain/internal/pkg/auth"

"github.com/labstack/echo/v4"
)

func LoginHandler(c echo.Context) error {
// return web/login.html page
return c.File("web/login.html")
}

func OAuthLoginHandler(c echo.Context) error {
provider := c.Param("provider")
ctx := c.Request().Context()
redirectUrl, err := auth.GetOAuth2RedirectURL(ctx, provider)
if err != nil {
return ErrorResponse(c, http.StatusInternalServerError, fmt.Errorf("failed to get oauth redirect url: %w", err))
}

return c.Redirect(http.StatusTemporaryRedirect, redirectUrl)
}

func OAuthCallbackHandler(c echo.Context) error {
provider := c.Param("provider")
code := c.QueryParam("code")
ctx := c.Request().Context()

token, err := auth.GetOAuth2Token(ctx, provider, code)
if err != nil {
return ErrorResponse(c, http.StatusInternalServerError, fmt.Errorf("failed to get oauth token: %w", err))
}

// TODO: get user info from database
userId := "userId"

jwtUser := auth.JwtUser{
UserID: userId,
OAuthProvider: provider,
AccessToken: token.AccessToken,
TokenType: token.TokenType,
Expiry: token.Expiry,
}

jwtToken, err := auth.GenerateJWT(jwtUser)
if err != nil {
return ErrorResponse(c, http.StatusInternalServerError, fmt.Errorf("failed to generate jwt token: %w", err))
}

// write jwt token to cookie
cookie := new(http.Cookie)
cookie.Name = "token"
cookie.Value = jwtToken
cookie.Expires = token.Expiry
c.SetCookie(cookie)
return JsonResponse(c, http.StatusOK, map[string]interface{}{
"jwt_token": jwtToken,
"jwt_user": jwtUser,
})
}
4 changes: 2 additions & 2 deletions internal/port/httpserver/handlers/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ func JsonResponse(c echo.Context, code int, data interface{}) error {
})
}

func ErrorResponse(c echo.Context, code int, errs ...error) error {
func ErrorResponse(c echo.Context, code int, err error) error {
return c.JSON(code, map[string]interface{}{
"success": false,
"errors": errs,
"errors": err.Error(),
"data": nil,
})
}
12 changes: 11 additions & 1 deletion internal/port/httpserver/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,22 @@ import (

func registerRouters(e *echo.Echo) {
v1Api := e.Group("/api/v1")
apiToolsRouters(v1Api)
authRouters(e.Group("/oauth"))
}

func apiToolsRouters(e *echo.Group) {
tools := e.Group("/tools")

tools := v1Api.Group("/tools")
tools.GET("/web/reader", handlers.WebReaderHandler)
tools.POST("/web/reader", handlers.WebReaderHandler)
tools.GET("/web/search", handlers.WebSearchHandler)
tools.POST("/web/search", handlers.WebSearchHandler)
tools.GET("/web/summary", handlers.WebSummaryHandler)
tools.POST("/web/summary", handlers.WebSummaryHandler)
}

func authRouters(oauth *echo.Group) {
oauth.GET("/:provider/login", handlers.OAuthLoginHandler)
oauth.GET("/:provider/callback", handlers.OAuthCallbackHandler)
}
17 changes: 9 additions & 8 deletions internal/port/httpserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"
"vibrain/internal/pkg/config"
"vibrain/internal/pkg/logger"
"vibrain/web"

"github.com/labstack/echo/v4"
)
Expand All @@ -19,31 +20,31 @@ func New() *echo.Echo {
e.GET("/status", func(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{"status": "ok"})
})
// static files
e.GET("/*", echo.WrapHandler(http.FileServer(web.StaticHttpFS)))
return e
}



type HttpService struct {
e *echo.Echo
E *echo.Echo
}


func NewServer() (*HttpService, error) {
return &HttpService{
e: New(),
E: New(),
}, nil
}

func (s *HttpService) Start(ctx context.Context) {
addr := fmt.Sprintf(":%d", config.Settings.Port)
if err := s.e.Start(addr); err != nil {
addr := fmt.Sprintf("localhost:%d", config.Settings.Port)

if err := s.E.Start(addr); err != nil {
logger.Default.Fatal("failed to start", "service", s.Name(), "addr", addr, "error", err)
}
}

func (s *HttpService) Stop(ctx context.Context) {
if err := s.e.Shutdown(ctx); err != nil {
if err := s.E.Shutdown(ctx); err != nil {
logger.Default.Fatal("failed to stop", "service", s.Name(), "error", err)
}
}
Expand Down
Loading

0 comments on commit ade5aef

Please sign in to comment.