fix: go mod oauth test
This commit is contained in:
parent
548266fe9d
commit
92704e80f1
8 changed files with 175 additions and 2 deletions
|
|
@ -14,4 +14,9 @@
|
||||||
| SMTP_PASSWORD | SMTP Password | `none` |
|
| SMTP_PASSWORD | SMTP Password | `none` |
|
||||||
| SMTP_FROM | Mail address used as from for all mails | `none` |
|
| SMTP_FROM | Mail address used as from for all mails | `none` |
|
||||||
| WEB_PUBLIC_REGISTRATION | Enabled the registration for anyone on the webgui | False |
|
| WEB_PUBLIC_REGISTRATION | Enabled the registration for anyone on the webgui | False |
|
||||||
|
| WEB_JWT_SECRET_KEY | Set secret key for the jwt used | `abc` |
|
||||||
| MINIAUTH_LOGIN_WITH_NOT_APPROVED_MAIL | Allow user to login even if there mail address is not validated | False |
|
| MINIAUTH_LOGIN_WITH_NOT_APPROVED_MAIL | Allow user to login even if there mail address is not validated | False |
|
||||||
|
|
||||||
|
|
||||||
|
## Development
|
||||||
|
create locale db by run `sh db.sh` than set the env varieables you need and run everything.
|
||||||
|
|
|
||||||
5
main.go
5
main.go
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"html/template"
|
"html/template"
|
||||||
|
|
||||||
"git.keks.cloud/kekskurse/miniauth/pkg/miniauth"
|
"git.keks.cloud/kekskurse/miniauth/pkg/miniauth"
|
||||||
|
"git.keks.cloud/kekskurse/miniauth/pkg/oauthapi"
|
||||||
"git.keks.cloud/kekskurse/miniauth/pkg/smtpclient"
|
"git.keks.cloud/kekskurse/miniauth/pkg/smtpclient"
|
||||||
"git.keks.cloud/kekskurse/miniauth/pkg/userstore"
|
"git.keks.cloud/kekskurse/miniauth/pkg/userstore"
|
||||||
"git.keks.cloud/kekskurse/miniauth/pkg/web"
|
"git.keks.cloud/kekskurse/miniauth/pkg/web"
|
||||||
|
|
@ -31,6 +32,7 @@ type gloableConfig struct {
|
||||||
WebConfig web.WebConfig `env:", prefix=WEB_"`
|
WebConfig web.WebConfig `env:", prefix=WEB_"`
|
||||||
MiniauthConfig miniauth.MiniauthConfig `env:", prefix=MINIAUTH_"`
|
MiniauthConfig miniauth.MiniauthConfig `env:", prefix=MINIAUTH_"`
|
||||||
DummyDatabase bool `env:"DUMMY_DATABASE"`
|
DummyDatabase bool `env:"DUMMY_DATABASE"`
|
||||||
|
OauthConfig oauthapi.OauthAPIConf `env:", prefix=OAUTH_"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func config() gloableConfig {
|
func config() gloableConfig {
|
||||||
|
|
@ -79,6 +81,9 @@ func setupRouter(cfg gloableConfig) *gin.Engine {
|
||||||
webServer := web.NewWeb(cfg.WebConfig, ma)
|
webServer := web.NewWeb(cfg.WebConfig, ma)
|
||||||
webServer.RegisterRoutes(routesWeb)
|
webServer.RegisterRoutes(routesWeb)
|
||||||
|
|
||||||
|
oauthapi := oauthapi.NewOauthAPI(cfg.OauthConfig, ma)
|
||||||
|
oauthapi.RegisterRoutes(router.Group("/oauth/"))
|
||||||
|
|
||||||
router.GET("/", func(c *gin.Context) {})
|
router.GET("/", func(c *gin.Context) {})
|
||||||
router.GET("/ping", func(c *gin.Context) { c.String(200, "pong") })
|
router.GET("/ping", func(c *gin.Context) { c.String(200, "pong") })
|
||||||
|
|
||||||
|
|
|
||||||
1
pkg/oauthapi/migrations/1742764325_init.down.sql
Normal file
1
pkg/oauthapi/migrations/1742764325_init.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
-- Write your down sql migration here
|
||||||
12
pkg/oauthapi/migrations/1742764325_init.up.sql
Normal file
12
pkg/oauthapi/migrations/1742764325_init.up.sql
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
-- Write your up sql migration here
|
||||||
|
CREATE TABLE clients (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
client_id TEXT NOT NULL UNIQUE,
|
||||||
|
client_secret TEXT NOT NULL UNIQUE,
|
||||||
|
redirect_uris TEXT NOT NULL,
|
||||||
|
response_types TEXT NOT NULL,
|
||||||
|
grant_types TEXT NOT NULL,
|
||||||
|
scopes TEXT NOT NULL
|
||||||
|
)
|
||||||
109
pkg/oauthapi/oauthapi.go
Normal file
109
pkg/oauthapi/oauthapi.go
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
package oauthapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.keks.cloud/kekskurse/miniauth/pkg/miniauth"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/ory/fosite"
|
||||||
|
"github.com/ory/fosite/compose"
|
||||||
|
"github.com/ory/fosite/storage"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OauthAPIConf struct{}
|
||||||
|
|
||||||
|
type OAuthAPI struct {
|
||||||
|
config OauthAPIConf
|
||||||
|
ma miniauth.Miniauth
|
||||||
|
log zerolog.Logger
|
||||||
|
oauth fosite.OAuth2Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOauthAPI(config OauthAPIConf, ma miniauth.Miniauth) OAuthAPI {
|
||||||
|
w := OAuthAPI{}
|
||||||
|
w.config = config
|
||||||
|
w.ma = ma
|
||||||
|
l := log.With().Str("pkg", "oauthapi").Logger()
|
||||||
|
w.log = l
|
||||||
|
|
||||||
|
storage := storage.NewExampleStore()
|
||||||
|
secret := []byte("my super secret signing password")
|
||||||
|
|
||||||
|
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
w.log.Fatal().Err(err).Msg("cant create privatekey")
|
||||||
|
}
|
||||||
|
|
||||||
|
oconfig := &fosite.Config{
|
||||||
|
AccessTokenLifespan: time.Minute * 30,
|
||||||
|
GlobalSecret: secret,
|
||||||
|
}
|
||||||
|
|
||||||
|
oauth2Provider := compose.ComposeAllEnabled(oconfig, storage, privateKey)
|
||||||
|
|
||||||
|
w.oauth = oauth2Provider
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w OAuthAPI) RegisterRoutes(routing *gin.RouterGroup) error {
|
||||||
|
routing.GET("/auth", w.authGet)
|
||||||
|
routing.POST("/auth", w.authPost)
|
||||||
|
routing.POST("/token", w.token)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w OAuthAPI) authGet(ctx *gin.Context) {
|
||||||
|
ar, err := w.oauth.NewAuthorizeRequest(ctx, ctx.Request)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("cant create authorize request")
|
||||||
|
w.oauth.WriteAuthorizeError(ctx, ctx.Writer, ar, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.log.Debug().Interface("ar", err).Msg("AuthorizeRequest")
|
||||||
|
|
||||||
|
ctx.HTML(http.StatusOK, "login.html", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w OAuthAPI) authPost(ctx *gin.Context) {
|
||||||
|
ar, err := w.oauth.NewAuthorizeRequest(ctx, ctx.Request)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("cant create authorize request")
|
||||||
|
w.oauth.WriteAuthorizeError(ctx, ctx.Writer, ar, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mySessionData := &fosite.DefaultSession{
|
||||||
|
Username: ctx.PostForm("username"),
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := w.oauth.NewAuthorizeResponse(ctx.Request.Context(), ar, mySessionData)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("cant create response")
|
||||||
|
w.oauth.WriteAuthorizeError(ctx, ctx.Writer, ar, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.oauth.WriteAuthorizeResponse(ctx, ctx.Writer, ar, response)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w OAuthAPI) token(ctx *gin.Context) {
|
||||||
|
mySessionData := new(fosite.DefaultSession)
|
||||||
|
|
||||||
|
accessRequest, err := w.oauth.NewAccessRequest(ctx, ctx.Request, mySessionData)
|
||||||
|
if err != nil {
|
||||||
|
w.oauth.WriteAccessError(ctx.Request.Context(), ctx.Writer, accessRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := w.oauth.NewAccessResponse(ctx, accessRequest)
|
||||||
|
if err != nil {
|
||||||
|
w.oauth.WriteAccessError(ctx, ctx.Writer, accessRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.oauth.WriteAccessResponse(ctx, ctx.Writer, accessRequest, response)
|
||||||
|
}
|
||||||
10
pkg/oauthapi/store.go
Normal file
10
pkg/oauthapi/store.go
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
package oauthapi
|
||||||
|
|
||||||
|
import "github.com/rs/zerolog"
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
SQLite struct {
|
||||||
|
Path string `env:"SQLITE_PATH"`
|
||||||
|
}
|
||||||
|
Logger zerolog.Logger
|
||||||
|
}
|
||||||
|
|
@ -3,13 +3,17 @@ package web
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.keks.cloud/kekskurse/miniauth/pkg/miniauth"
|
"git.keks.cloud/kekskurse/miniauth/pkg/miniauth"
|
||||||
|
"git.keks.cloud/kekskurse/miniauth/pkg/userstore"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WebConfig struct {
|
type WebConfig struct {
|
||||||
PublicRegistration bool `env:"PUBLIC_REGISTRATION, default=0"`
|
PublicRegistration bool `env:"PUBLIC_REGISTRATION, default=0"`
|
||||||
|
JWT_SECRET_KEY string `env:"JWT_SECRET_KEY, default=abc"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Web struct {
|
type Web struct {
|
||||||
|
|
@ -68,11 +72,37 @@ func (w Web) PostLoginPage(c *gin.Context) {
|
||||||
username := c.PostForm("username")
|
username := c.PostForm("username")
|
||||||
password := c.PostForm("password")
|
password := c.PostForm("password")
|
||||||
|
|
||||||
err := w.ma.UserLogin(username, password)
|
_, err := w.ma.UserLogin(username, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.HTML(http.StatusOK, "login.html", gin.H{"msg": errors.Unwrap(err).Error()})
|
c.HTML(http.StatusOK, "login.html", gin.H{"msg": errors.Unwrap(err).Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.SetSameSite(http.SameSiteStrictMode)
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "msg.html", gin.H{"msg": "Login ok!"})
|
c.HTML(http.StatusOK, "msg.html", gin.H{"msg": "Login ok!"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w Web) DashboardPage(c *gin.Context) {
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserClaim struct {
|
||||||
|
User userstore.User
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w Web) createToken(user userstore.User) (string, error) {
|
||||||
|
tokenLifespan := 15 * time.Minute
|
||||||
|
|
||||||
|
claims := UserClaim{
|
||||||
|
User: user,
|
||||||
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
|
Subject: "user-" + user.Username,
|
||||||
|
Issuer: "gin-jwt",
|
||||||
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(tokenLifespan)),
|
||||||
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
return t.SignedString(w.config.JWT_SECRET_KEY)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ export default defineConfig({
|
||||||
|
|
||||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
trace: 'on-first-retry',
|
trace: 'on-first-retry',
|
||||||
|
video: 'on',
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Configure projects for major browsers */
|
/* Configure projects for major browsers */
|
||||||
|
|
|
||||||
Reference in a new issue