feat: persist sessions and auto redirect to app

This commit is contained in:
Stavros
2025-01-24 15:29:46 +02:00
parent 80d25551e0
commit 433e71bd50
11 changed files with 287 additions and 88 deletions

View File

@@ -36,6 +36,7 @@ type API struct {
Hooks *hooks.Hooks
Auth *auth.Auth
Providers *providers.Providers
Domain string
}
func (api *API) Init() {
@@ -70,8 +71,10 @@ func (api *API) Init() {
isSecure = false
}
api.Domain = fmt.Sprintf(".%s", domain)
store.Options(sessions.Options{
Domain: fmt.Sprintf(".%s", domain),
Domain: api.Domain,
Path: "/",
HttpOnly: true,
Secure: isSecure,
@@ -163,8 +166,7 @@ func (api *API) SetupRoutes() {
}
session := sessions.Default(c)
session.Set("tinyauth_sid", user.Email)
session.Set("tinyauth_oauth_provider", "")
session.Set("tinyauth_sid", fmt.Sprintf("email:%s", login.Email))
session.Save()
c.JSON(200, gin.H{
@@ -176,9 +178,10 @@ func (api *API) SetupRoutes() {
api.Router.POST("/api/logout", func(c *gin.Context) {
session := sessions.Default(c)
session.Delete("tinyauth_sid")
session.Delete("tinyauth_oauth_provider")
session.Save()
c.SetCookie("tinyauth_redirect_uri", "", -1, "/", api.Domain, api.Config.CookieSecure, true)
c.JSON(200, gin.H{
"status": 200,
"message": "Logged out",
@@ -198,23 +201,25 @@ func (api *API) SetupRoutes() {
if !userContext.IsLoggedIn {
c.JSON(200, gin.H{
"status": 200,
"message": "Unauthenticated",
"email": "",
"isLoggedIn": false,
"oauth": false,
"provider": "",
"status": 200,
"message": "Unauthenticated",
"email": "",
"isLoggedIn": false,
"oauth": false,
"provider": "",
"configuredProviders": api.Providers.GetConfiguredProviders(),
})
return
}
c.JSON(200, gin.H{
"status": 200,
"message": "Authenticated",
"email": userContext.Email,
"isLoggedIn": userContext.IsLoggedIn,
"oauth": userContext.OAuth,
"provider": userContext.Provider,
"status": 200,
"message": "Authenticated",
"email": userContext.Email,
"isLoggedIn": userContext.IsLoggedIn,
"oauth": userContext.OAuth,
"provider": userContext.Provider,
"configuredProviders": api.Providers.GetConfiguredProviders(),
})
})
@@ -226,9 +231,9 @@ func (api *API) SetupRoutes() {
})
api.Router.GET("/api/oauth/url/:provider", func(c *gin.Context) {
var provider types.OAuthBind
var request types.OAuthRequest
bindErr := c.BindUri(&provider)
bindErr := c.BindUri(&request)
if bindErr != nil {
c.JSON(400, gin.H{
@@ -238,16 +243,24 @@ func (api *API) SetupRoutes() {
return
}
authURL := api.Providers.GetAuthURL(provider.Provider)
provider := api.Providers.GetProvider(request.Provider)
if authURL == "" {
c.JSON(400, gin.H{
"status": 400,
"message": "Bad Request",
if provider == nil {
c.JSON(404, gin.H{
"status": 404,
"message": "Not Found",
})
return
}
authURL := provider.GetAuthURL()
redirectURI := c.Query("redirect_uri")
if redirectURI != "" {
c.SetCookie("tinyauth_redirect_uri", redirectURI, 3600, "/", api.Domain, api.Config.CookieSecure, true)
}
c.JSON(200, gin.H{
"status": 200,
"message": "Ok",
@@ -256,9 +269,9 @@ func (api *API) SetupRoutes() {
})
api.Router.GET("/api/oauth/callback/:provider", func(c *gin.Context) {
var provider types.OAuthBind
var providerName types.OAuthRequest
bindErr := c.BindUri(&provider)
bindErr := c.BindUri(&providerName)
if bindErr != nil {
c.JSON(400, gin.H{
@@ -278,9 +291,19 @@ func (api *API) SetupRoutes() {
return
}
email, emailErr := api.Providers.Login(code, provider.Provider)
provider := api.Providers.GetProvider(providerName.Provider)
if emailErr != nil {
if provider == nil {
c.JSON(404, gin.H{
"status": 404,
"message": "Not Found",
})
return
}
token, tokenErr := provider.ExchangeToken(code)
if tokenErr != nil {
c.JSON(500, gin.H{
"status": 500,
"message": "Internal Server Error",
@@ -289,14 +312,33 @@ func (api *API) SetupRoutes() {
}
session := sessions.Default(c)
session.Set("tinyauth_sid", email)
session.Set("tinyauth_oauth_provider", provider.Provider)
session.Set("tinyauth_sid", fmt.Sprintf("%s:%s", providerName.Provider, token))
session.Save()
c.JSON(200, gin.H{
"status": 200,
"message": "Logged in",
redirectURI, redirectURIErr := c.Cookie("tinyauth_redirect_uri")
if redirectURIErr != nil {
c.JSON(200, gin.H{
"status": 200,
"message": "Logged in",
})
}
c.SetCookie("tinyauth_redirect_uri", "", -1, "/", api.Domain, api.Config.CookieSecure, true)
queries, queryErr := query.Values(types.LoginQuery{
RedirectURI: redirectURI,
})
if queryErr != nil {
c.JSON(501, gin.H{
"status": 501,
"message": "Internal Server Error",
})
return
}
c.Redirect(http.StatusPermanentRedirect, fmt.Sprintf("%s/continue?%s", api.Config.AppURL, queries.Encode()))
})
}

View File

@@ -1,12 +1,14 @@
package hooks
import (
"strings"
"tinyauth/internal/auth"
"tinyauth/internal/providers"
"tinyauth/internal/types"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"golang.org/x/oauth2"
)
func NewHooks(auth *auth.Auth, providers *providers.Providers) *Hooks {
@@ -24,7 +26,6 @@ type Hooks struct {
func (hooks *Hooks) UseUserContext(c *gin.Context) (types.UserContext, error) {
session := sessions.Default(c)
sessionCookie := session.Get("tinyauth_sid")
oauthProviderCookie := session.Get("tinyauth_oauth_provider")
if sessionCookie == nil {
return types.UserContext{
@@ -35,19 +36,33 @@ func (hooks *Hooks) UseUserContext(c *gin.Context) (types.UserContext, error) {
}, nil
}
email, emailOk := sessionCookie.(string)
provider, providerOk := oauthProviderCookie.(string)
data, dataOk := sessionCookie.(string)
if provider == "" || !providerOk {
if !emailOk {
return types.UserContext{
Email: "",
IsLoggedIn: false,
OAuth: false,
Provider: "",
}, nil
}
user := hooks.Auth.GetUser(email)
if !dataOk {
return types.UserContext{
Email: "",
IsLoggedIn: false,
OAuth: false,
Provider: "",
}, nil
}
split := strings.Split(data, ":")
if len(split) != 2 {
return types.UserContext{
Email: "",
IsLoggedIn: false,
OAuth: false,
Provider: "",
}, nil
}
sessionType := split[0]
sessionValue := split[1]
if sessionType == "email" {
user := hooks.Auth.GetUser(sessionValue)
if user == nil {
return types.UserContext{
Email: "",
@@ -57,16 +72,31 @@ func (hooks *Hooks) UseUserContext(c *gin.Context) (types.UserContext, error) {
}, nil
}
return types.UserContext{
Email: email,
Email: sessionValue,
IsLoggedIn: true,
OAuth: false,
Provider: "",
}, nil
}
oauthEmail, oauthEmailErr := hooks.Providers.GetUser(provider)
provider := hooks.Providers.GetProvider(sessionType)
if oauthEmailErr != nil {
if provider == nil {
return types.UserContext{
Email: "",
IsLoggedIn: false,
OAuth: false,
Provider: "",
}, nil
}
provider.Token = &oauth2.Token{
AccessToken: sessionValue,
}
email, emailErr := hooks.Providers.GetUser(sessionType)
if emailErr != nil {
return types.UserContext{
Email: "",
IsLoggedIn: false,
@@ -76,9 +106,9 @@ func (hooks *Hooks) UseUserContext(c *gin.Context) (types.UserContext, error) {
}
return types.UserContext{
Email: oauthEmail,
Email: email,
IsLoggedIn: true,
OAuth: true,
Provider: provider,
Provider: sessionType,
}, nil
}

View File

@@ -30,14 +30,14 @@ func (oauth *OAuth) GetAuthURL() string {
return oauth.Config.AuthCodeURL("state", oauth2.AccessTypeOffline, oauth2.S256ChallengeOption(oauth.Verifier))
}
func (oauth *OAuth) ExchangeToken(code string) error {
func (oauth *OAuth) ExchangeToken(code string) (string, error) {
token, err := oauth.Config.Exchange(oauth.Context, code, oauth2.VerifierOption(oauth.Verifier))
if err != nil {
log.Error().Err(err).Msg("Failed to exchange code")
return err
return "", err
}
oauth.Token = token
return nil
return oauth.Token.AccessToken, nil
}
func (oauth *OAuth) GetClient() *http.Client {

View File

@@ -35,24 +35,12 @@ func (providers *Providers) Init() {
}
}
func (providers *Providers) Login(code string, provider string) (string, error) {
func (providers *Providers) GetProvider(provider string) *oauth.OAuth {
switch provider {
case "github":
if providers.Github == nil {
return "", nil
}
exchangeErr := providers.Github.ExchangeToken(code)
if exchangeErr != nil {
return "", exchangeErr
}
client := providers.Github.GetClient()
email, emailErr := GetGithubEmail(client)
if emailErr != nil {
return "", emailErr
}
return email, nil
return providers.Github
default:
return "", nil
return nil
}
}
@@ -73,14 +61,10 @@ func (providers *Providers) GetUser(provider string) (string, error) {
}
}
func (providers *Providers) GetAuthURL(provider string) string {
switch provider {
case "github":
if providers.Github == nil {
return ""
}
return providers.Github.GetAuthURL()
default:
return ""
func (provider *Providers) GetConfiguredProviders() []string {
providers := []string{}
if provider.Github != nil {
providers = append(providers, "github")
}
return providers
}

View File

@@ -58,7 +58,7 @@ type OAuthConfig struct {
MicrosoftClientSecret string
}
type OAuthBind struct {
type OAuthRequest struct {
Provider string `uri:"provider" binding:"required"`
}
@@ -67,8 +67,3 @@ type OAuthProviders struct {
Google *oauth.OAuth
Microsoft *oauth.OAuth
}
type OAuthLogin struct {
Email string
Token string
}

View File

@@ -22,7 +22,7 @@ func ParseUsers(users string) (types.Users, error) {
return types.Users{}, errors.New("invalid user format")
}
usersParsed = append(usersParsed, types.User{
Email: userSplit[0],
Email: userSplit[0],
Password: userSplit[1],
})
}