diff --git a/Dockerfile b/Dockerfile
index eb0b3a0..1f4c1c0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -38,7 +38,7 @@ COPY --from=site-builder /site/dist ./internal/assets/dist
RUN go build
# Runner
-FROM busybox:1.37-musl AS runner
+FROM alpine:3.21 AS runner
WORKDIR /tinyauth
diff --git a/cmd/root.go b/cmd/root.go
index 576fc82..6193806 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -7,6 +7,7 @@ import (
"tinyauth/internal/api"
"tinyauth/internal/auth"
"tinyauth/internal/hooks"
+ "tinyauth/internal/providers"
"tinyauth/internal/types"
"tinyauth/internal/utils"
@@ -19,7 +20,7 @@ import (
var rootCmd = &cobra.Command{
Use: "tinyauth",
Short: "An extremely simple traefik forward auth proxy.",
- Long: `Tinyauth is an extremely simple traefik forward-auth login screen that makes securing your apps easy.`,
+ Long: `Tinyauth is an extremely simple traefik forward-auth login screen that makes securing your apps easy.`,
Run: func(cmd *cobra.Command, args []string) {
// Get config
log.Info().Msg("Parsing config")
@@ -58,20 +59,36 @@ var rootCmd = &cobra.Command{
users, parseErr := utils.ParseUsers(usersString)
HandleError(parseErr, "Failed to parse users")
+ // Create OAuth config
+ oauthConfig := types.OAuthConfig{
+ GithubClientId: config.GithubClientId,
+ GithubClientSecret: config.GithubClientSecret,
+ GoogleClientId: config.GoogleClientId,
+ GoogleClientSecret: config.GoogleClientSecret,
+ MicrosoftClientId: config.MicrosoftClientId,
+ MicrosoftClientSecret: config.MicrosoftClientSecret,
+ }
+
// Create auth service
auth := auth.NewAuth(users)
-
+
+ // Create OAuth providers service
+ providers := providers.NewProviders(oauthConfig)
+
+ // Initialize providers
+ providers.Init()
+
// Create hooks service
- hooks := hooks.NewHooks(auth)
-
+ hooks := hooks.NewHooks(auth, providers)
+
// Create API
api := api.NewAPI(types.APIConfig{
- Port: config.Port,
- Address: config.Address,
- Secret: config.Secret,
- AppURL: config.AppURL,
+ Port: config.Port,
+ Address: config.Address,
+ Secret: config.Secret,
+ AppURL: config.AppURL,
CookieSecure: config.CookieSecure,
- }, hooks, auth)
+ }, hooks, auth, providers)
// Setup routes
api.Init()
@@ -107,6 +124,12 @@ func init() {
rootCmd.Flags().String("users", "", "Comma separated list of users in the format username:bcrypt-hashed-password.")
rootCmd.Flags().String("users-file", "", "Path to a file containing users in the format username:bcrypt-hashed-password.")
rootCmd.Flags().Bool("cookie-secure", false, "Send cookie over secure connection only.")
+ rootCmd.Flags().String("github-client-id", "", "Github OAuth client ID.")
+ rootCmd.Flags().String("github-client-secret", "", "Github OAuth client secret.")
+ rootCmd.Flags().String("google-client-id", "", "Google OAuth client ID.")
+ rootCmd.Flags().String("google-client-secret", "", "Google OAuth client secret.")
+ rootCmd.Flags().String("microsoft-client-id", "", "Microsoft OAuth client ID.")
+ rootCmd.Flags().String("microsoft-client-secret", "", "Microsoft OAuth client secret.")
viper.BindEnv("port", "PORT")
viper.BindEnv("address", "ADDRESS")
viper.BindEnv("secret", "SECRET")
@@ -114,5 +137,11 @@ func init() {
viper.BindEnv("users", "USERS")
viper.BindEnv("users-file", "USERS_FILE")
viper.BindEnv("cookie-secure", "COOKIE_SECURE")
+ viper.BindEnv("github-client-id", "GITHUB_CLIENT_ID")
+ viper.BindEnv("github-client-secret", "GITHUB_CLIENT_SECRET")
+ viper.BindEnv("google-client-id", "GOOGLE_CLIENT_ID")
+ viper.BindEnv("google-client-secret", "GOOGLE_CLIENT_SECRET")
+ viper.BindEnv("microsoft-client-id", "MICROSOFT_CLIENT_ID")
+ viper.BindEnv("microsoft-client-secret", "MICROSOFT_CLIENT_SECRET")
viper.BindPFlags(rootCmd.Flags())
}
diff --git a/go.mod b/go.mod
index e8fe7a7..7d68c51 100644
--- a/go.mod
+++ b/go.mod
@@ -72,6 +72,7 @@ require (
golang.org/x/arch v0.13.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.34.0 // indirect
+ golang.org/x/oauth2 v0.25.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/text v0.21.0 // indirect
diff --git a/go.sum b/go.sum
index 0887ca6..71ce8ac 100644
--- a/go.sum
+++ b/go.sum
@@ -180,6 +180,8 @@ golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjs
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
+golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
+golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/internal/api/api.go b/internal/api/api.go
index 360aa3c..eb8d46e 100644
--- a/internal/api/api.go
+++ b/internal/api/api.go
@@ -10,6 +10,7 @@ import (
"tinyauth/internal/assets"
"tinyauth/internal/auth"
"tinyauth/internal/hooks"
+ "tinyauth/internal/providers"
"tinyauth/internal/types"
"tinyauth/internal/utils"
@@ -20,25 +21,26 @@ import (
"github.com/rs/zerolog/log"
)
-func NewAPI(config types.APIConfig, hooks *hooks.Hooks, auth *auth.Auth) (*API) {
+func NewAPI(config types.APIConfig, hooks *hooks.Hooks, auth *auth.Auth, providers *providers.Providers) *API {
return &API{
- Config: config,
- Hooks: hooks,
- Auth: auth,
- Router: nil,
+ Config: config,
+ Hooks: hooks,
+ Auth: auth,
+ Providers: providers,
}
}
type API struct {
- Config types.APIConfig
- Router *gin.Engine
- Hooks *hooks.Hooks
- Auth *auth.Auth
+ Config types.APIConfig
+ Router *gin.Engine
+ Hooks *hooks.Hooks
+ Auth *auth.Auth
+ Providers *providers.Providers
}
func (api *API) Init() {
gin.SetMode(gin.ReleaseMode)
-
+
router := gin.New()
router.Use(zerolog())
dist, distErr := fs.Sub(assets.Assets, "dist")
@@ -67,17 +69,17 @@ func (api *API) Init() {
} else {
isSecure = false
}
-
- store.Options(sessions.Options{
- Domain: fmt.Sprintf(".%s", domain),
- Path: "/",
- HttpOnly: true,
- Secure: isSecure,
- })
-
- router.Use(sessions.Sessions("tinyauth", store))
- router.Use(func(c *gin.Context) {
+ store.Options(sessions.Options{
+ Domain: fmt.Sprintf(".%s", domain),
+ Path: "/",
+ HttpOnly: true,
+ Secure: isSecure,
+ })
+
+ router.Use(sessions.Sessions("tinyauth", store))
+
+ router.Use(func(c *gin.Context) {
if !strings.HasPrefix(c.Request.URL.Path, "/api") {
_, err := fs.Stat(dist, strings.TrimPrefix(c.Request.URL.Path, "/"))
if os.IsNotExist(err) {
@@ -92,12 +94,20 @@ func (api *API) Init() {
}
func (api *API) SetupRoutes() {
- api.Router.GET("/api/auth", func (c *gin.Context) {
- userContext := api.Hooks.UseUserContext(c)
+ api.Router.GET("/api/auth", func(c *gin.Context) {
+ userContext, userContextErr := api.Hooks.UseUserContext(c)
+
+ if userContextErr != nil {
+ c.JSON(500, gin.H{
+ "status": 500,
+ "message": "Internal Server Error",
+ })
+ return
+ }
if userContext.IsLoggedIn {
c.JSON(200, gin.H{
- "status": 200,
+ "status": 200,
"message": "Authenticated",
})
return
@@ -112,7 +122,7 @@ func (api *API) SetupRoutes() {
if queryErr != nil {
c.JSON(501, gin.H{
- "status": 501,
+ "status": 501,
"message": "Internal Server Error",
})
return
@@ -121,24 +131,24 @@ func (api *API) SetupRoutes() {
c.Redirect(http.StatusTemporaryRedirect, fmt.Sprintf("%s/?%s", api.Config.AppURL, queries.Encode()))
})
- api.Router.POST("/api/login", func (c *gin.Context) {
+ api.Router.POST("/api/login", func(c *gin.Context) {
var login types.LoginRequest
err := c.BindJSON(&login)
if err != nil {
c.JSON(400, gin.H{
- "status": 400,
+ "status": 400,
"message": "Bad Request",
})
return
}
- user := api.Auth.GetUser(login.Username)
+ user := api.Auth.GetUser(login.Email)
if user == nil {
c.JSON(401, gin.H{
- "status": 401,
+ "status": 401,
"message": "Unauthorized",
})
return
@@ -146,62 +156,149 @@ func (api *API) SetupRoutes() {
if !api.Auth.CheckPassword(*user, login.Password) {
c.JSON(401, gin.H{
- "status": 401,
+ "status": 401,
"message": "Unauthorized",
})
return
}
session := sessions.Default(c)
- session.Set("tinyauth", user.Username)
+ session.Set("tinyauth_sid", user.Email)
+ session.Set("tinyauth_oauth_provider", "")
session.Save()
c.JSON(200, gin.H{
- "status": 200,
+ "status": 200,
"message": "Logged in",
})
})
- api.Router.POST("/api/logout", func (c *gin.Context) {
+ api.Router.POST("/api/logout", func(c *gin.Context) {
session := sessions.Default(c)
- session.Delete("tinyauth")
+ session.Delete("tinyauth_sid")
+ session.Delete("tinyauth_oauth_provider")
session.Save()
c.JSON(200, gin.H{
- "status": 200,
+ "status": 200,
"message": "Logged out",
})
})
- api.Router.GET("/api/status", func (c *gin.Context) {
- userContext := api.Hooks.UseUserContext(c)
+ api.Router.GET("/api/status", func(c *gin.Context) {
+ userContext, userContextErr := api.Hooks.UseUserContext(c)
+
+ if userContextErr != nil {
+ c.JSON(500, gin.H{
+ "status": 500,
+ "message": "Internal Server Error",
+ })
+ return
+ }
if !userContext.IsLoggedIn {
c.JSON(200, gin.H{
- "status": 200,
- "message": "Unauthenticated",
- "username": "",
+ "status": 200,
+ "message": "Unauthenticated",
+ "email": "",
"isLoggedIn": false,
+ "oauth": false,
+ "provider": "",
})
return
- }
+ }
c.JSON(200, gin.H{
- "status": 200,
- "message": "Authenticated",
- "username": userContext.Username,
- "isLoggedIn": true,
+ "status": 200,
+ "message": "Authenticated",
+ "email": userContext.Email,
+ "isLoggedIn": userContext.IsLoggedIn,
+ "oauth": userContext.OAuth,
+ "provider": userContext.Provider,
})
})
- api.Router.GET("/api/healthcheck", func (c *gin.Context) {
+ api.Router.GET("/api/healthcheck", func(c *gin.Context) {
c.JSON(200, gin.H{
- "status": 200,
+ "status": 200,
"message": "OK",
})
})
-}
+ api.Router.GET("/api/oauth/url/:provider", func(c *gin.Context) {
+ var provider types.OAuthBind
+
+ bindErr := c.BindUri(&provider)
+
+ if bindErr != nil {
+ c.JSON(400, gin.H{
+ "status": 400,
+ "message": "Bad Request",
+ })
+ return
+ }
+
+ authURL := api.Providers.GetAuthURL(provider.Provider)
+
+ if authURL == "" {
+ c.JSON(400, gin.H{
+ "status": 400,
+ "message": "Bad Request",
+ })
+ return
+ }
+
+ c.JSON(200, gin.H{
+ "status": 200,
+ "message": "Ok",
+ "url": authURL,
+ })
+ })
+
+ api.Router.GET("/api/oauth/callback/:provider", func(c *gin.Context) {
+ var provider types.OAuthBind
+
+ bindErr := c.BindUri(&provider)
+
+ if bindErr != nil {
+ c.JSON(400, gin.H{
+ "status": 400,
+ "message": "Bad Request",
+ })
+ return
+ }
+
+ code := c.Query("code")
+
+ if code == "" {
+ c.JSON(400, gin.H{
+ "status": 400,
+ "message": "Bad Request",
+ })
+ return
+ }
+
+ email, emailErr := api.Providers.Login(code, provider.Provider)
+
+ if emailErr != nil {
+ c.JSON(500, gin.H{
+ "status": 500,
+ "message": "Internal Server Error",
+ })
+ return
+ }
+
+ session := sessions.Default(c)
+ session.Set("tinyauth_sid", email)
+ session.Set("tinyauth_oauth_provider", provider.Provider)
+ session.Save()
+
+ c.JSON(200, gin.H{
+ "status": 200,
+ "message": "Logged in",
+ })
+ })
+}
func (api *API) Run() {
log.Info().Str("address", api.Config.Address).Int("port", api.Config.Port).Msg("Starting server")
@@ -218,16 +315,16 @@ func zerolog() gin.HandlerFunc {
address := c.Request.RemoteAddr
method := c.Request.Method
path := c.Request.URL.Path
-
+
latency := time.Since(tStart).String()
switch {
- case code >= 200 && code < 300:
- log.Info().Str("method", method).Str("path", path).Str("address", address).Int("status", code).Str("latency", latency).Msg("Request")
- case code >= 300 && code < 400:
- log.Warn().Str("method", method).Str("path", path).Str("address", address).Int("status", code).Str("latency", latency).Msg("Request")
- case code >= 400:
- log.Error().Str("method", method).Str("path", path).Str("address", address).Int("status", code).Str("latency", latency).Msg("Request")
+ case code >= 200 && code < 300:
+ log.Info().Str("method", method).Str("path", path).Str("address", address).Int("status", code).Str("latency", latency).Msg("Request")
+ case code >= 300 && code < 400:
+ log.Warn().Str("method", method).Str("path", path).Str("address", address).Int("status", code).Str("latency", latency).Msg("Request")
+ case code >= 400:
+ log.Error().Str("method", method).Str("path", path).Str("address", address).Int("status", code).Str("latency", latency).Msg("Request")
}
}
-}
\ No newline at end of file
+}
diff --git a/internal/auth/auth.go b/internal/auth/auth.go
index a5f0cef..a82f737 100644
--- a/internal/auth/auth.go
+++ b/internal/auth/auth.go
@@ -16,9 +16,9 @@ type Auth struct {
Users types.Users
}
-func (auth *Auth) GetUser(username string) *types.User {
+func (auth *Auth) GetUser(email string) *types.User {
for _, user := range auth.Users {
- if user.Username == username {
+ if user.Email == email {
return &user
}
}
diff --git a/internal/hooks/hooks.go b/internal/hooks/hooks.go
index d45d84f..0390cab 100644
--- a/internal/hooks/hooks.go
+++ b/internal/hooks/hooks.go
@@ -2,53 +2,83 @@ package hooks
import (
"tinyauth/internal/auth"
+ "tinyauth/internal/providers"
"tinyauth/internal/types"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)
-func NewHooks(auth *auth.Auth) *Hooks {
+func NewHooks(auth *auth.Auth, providers *providers.Providers) *Hooks {
return &Hooks{
- Auth: auth,
+ Auth: auth,
+ Providers: providers,
}
}
type Hooks struct {
- Auth *auth.Auth
+ Auth *auth.Auth
+ Providers *providers.Providers
}
-func (hooks *Hooks) UseUserContext(c *gin.Context) (types.UserContext) {
+func (hooks *Hooks) UseUserContext(c *gin.Context) (types.UserContext, error) {
session := sessions.Default(c)
- cookie := session.Get("tinyauth")
+ sessionCookie := session.Get("tinyauth_sid")
+ oauthProviderCookie := session.Get("tinyauth_oauth_provider")
- if cookie == nil {
+ if sessionCookie == nil {
return types.UserContext{
- Username: "",
+ Email: "",
IsLoggedIn: false,
- }
+ OAuth: false,
+ Provider: "",
+ }, nil
}
- username, ok := cookie.(string)
+ email, emailOk := sessionCookie.(string)
+ provider, providerOk := oauthProviderCookie.(string)
- if !ok {
- return types.UserContext{
- Username: "",
- IsLoggedIn: false,
+ if provider == "" || !providerOk {
+ if !emailOk {
+ return types.UserContext{
+ Email: "",
+ IsLoggedIn: false,
+ OAuth: false,
+ Provider: "",
+ }, nil
}
+ user := hooks.Auth.GetUser(email)
+ if user == nil {
+ return types.UserContext{
+ Email: "",
+ IsLoggedIn: false,
+ OAuth: false,
+ Provider: "",
+ }, nil
+ }
+ return types.UserContext{
+ Email: email,
+ IsLoggedIn: true,
+ OAuth: false,
+ Provider: "",
+ }, nil
}
- user := hooks.Auth.GetUser(username)
+ oauthEmail, oauthEmailErr := hooks.Providers.GetUser(provider)
- if user == nil {
+ if oauthEmailErr != nil {
return types.UserContext{
- Username: "",
+ Email: "",
IsLoggedIn: false,
- }
+ OAuth: false,
+ Provider: "",
+ }, nil
}
return types.UserContext{
- Username: username,
+ Email: oauthEmail,
IsLoggedIn: true,
- }
-}
\ No newline at end of file
+ OAuth: true,
+ Provider: provider,
+ }, nil
+}
diff --git a/internal/oauth/oauth.go b/internal/oauth/oauth.go
new file mode 100644
index 0000000..523cac2
--- /dev/null
+++ b/internal/oauth/oauth.go
@@ -0,0 +1,45 @@
+package oauth
+
+import (
+ "context"
+ "net/http"
+
+ "github.com/rs/zerolog/log"
+ "golang.org/x/oauth2"
+)
+
+func NewOAuth(config oauth2.Config) *OAuth {
+ return &OAuth{
+ Config: config,
+ }
+}
+
+type OAuth struct {
+ Config oauth2.Config
+ Context context.Context
+ Token *oauth2.Token
+ Verifier string
+}
+
+func (oauth *OAuth) Init() {
+ oauth.Context = context.Background()
+ oauth.Verifier = oauth2.GenerateVerifier()
+}
+
+func (oauth *OAuth) GetAuthURL() string {
+ return oauth.Config.AuthCodeURL("state", oauth2.AccessTypeOffline, oauth2.S256ChallengeOption(oauth.Verifier))
+}
+
+func (oauth *OAuth) ExchangeToken(code 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
+ }
+ oauth.Token = token
+ return nil
+}
+
+func (oauth *OAuth) GetClient() *http.Client {
+ return oauth.Config.Client(oauth.Context, oauth.Token)
+}
diff --git a/internal/providers/github.go b/internal/providers/github.go
new file mode 100644
index 0000000..00f988c
--- /dev/null
+++ b/internal/providers/github.go
@@ -0,0 +1,47 @@
+package providers
+
+import (
+ "encoding/json"
+ "errors"
+ "io"
+ "net/http"
+)
+
+type GithubEmailsResponse []struct {
+ Email string `json:"email"`
+ Primary bool `json:"primary"`
+}
+
+func GithubScopes() ([]string) {
+ return []string{"user:email"}
+}
+
+func GetGithubEmail(client *http.Client) (string, error) {
+ res, resErr := client.Get("https://api.github.com/user/emails")
+
+ if resErr != nil {
+ return "", resErr
+ }
+
+ body, bodyErr := io.ReadAll(res.Body)
+
+ if bodyErr != nil {
+ return "", bodyErr
+ }
+
+ var emails GithubEmailsResponse
+
+ jsonErr := json.Unmarshal(body, &emails)
+
+ if jsonErr != nil {
+ return "", jsonErr
+ }
+
+ for _, email := range emails {
+ if email.Primary {
+ return email.Email, nil
+ }
+ }
+
+ return "", errors.New("no primary email found")
+}
\ No newline at end of file
diff --git a/internal/providers/providers.go b/internal/providers/providers.go
new file mode 100644
index 0000000..0170335
--- /dev/null
+++ b/internal/providers/providers.go
@@ -0,0 +1,86 @@
+package providers
+
+import (
+ "tinyauth/internal/oauth"
+ "tinyauth/internal/types"
+
+ "github.com/rs/zerolog/log"
+ "golang.org/x/oauth2"
+ "golang.org/x/oauth2/endpoints"
+)
+
+func NewProviders(config types.OAuthConfig) *Providers {
+ return &Providers{
+ Config: config,
+ }
+}
+
+type Providers struct {
+ Config types.OAuthConfig
+ Github *oauth.OAuth
+ Google *oauth.OAuth
+ Microsoft *oauth.OAuth
+}
+
+func (providers *Providers) Init() {
+ if providers.Config.GithubClientId != "" && providers.Config.GithubClientSecret != "" {
+ log.Info().Msg("Initializing Github OAuth")
+ providers.Github = oauth.NewOAuth(oauth2.Config{
+ ClientID: providers.Config.GithubClientId,
+ ClientSecret: providers.Config.GithubClientSecret,
+ Scopes: GithubScopes(),
+ Endpoint: endpoints.GitHub,
+ })
+ providers.Github.Init()
+ }
+}
+
+func (providers *Providers) Login(code string, provider string) (string, error) {
+ 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
+ default:
+ return "", nil
+ }
+}
+
+func (providers *Providers) GetUser(provider string) (string, error) {
+ switch provider {
+ case "github":
+ if providers.Github == nil {
+ return "", nil
+ }
+ client := providers.Github.GetClient()
+ email, emailErr := GetGithubEmail(client)
+ if emailErr != nil {
+ return "", emailErr
+ }
+ return email, nil
+ default:
+ return "", nil
+ }
+}
+
+func (providers *Providers) GetAuthURL(provider string) string {
+ switch provider {
+ case "github":
+ if providers.Github == nil {
+ return ""
+ }
+ return providers.Github.GetAuthURL()
+ default:
+ return ""
+ }
+}
diff --git a/internal/types/types.go b/internal/types/types.go
index e1e3034..8c8d72d 100644
--- a/internal/types/types.go
+++ b/internal/types/types.go
@@ -1,40 +1,74 @@
package types
+import "tinyauth/internal/oauth"
+
type LoginQuery struct {
RedirectURI string `url:"redirect_uri"`
}
type LoginRequest struct {
- Username string `json:"username"`
+ Email string `json:"email"`
Password string `json:"password"`
}
type User struct {
- Username string
+ Email string
Password string
}
type Users []User
type Config struct {
- Port int `validate:"number" mapstructure:"port"`
- Address string `mapstructure:"address, ip4_addr"`
- Secret string `validate:"required,len=32" mapstructure:"secret"`
- AppURL string `validate:"required,url" mapstructure:"app-url"`
- Users string `mapstructure:"users"`
- UsersFile string `mapstructure:"users-file"`
- CookieSecure bool `mapstructure:"cookie-secure"`
+ Port int `validate:"number" mapstructure:"port"`
+ Address string `mapstructure:"address, ip4_addr"`
+ Secret string `validate:"required,len=32" mapstructure:"secret"`
+ AppURL string `validate:"required,url" mapstructure:"app-url"`
+ Users string `mapstructure:"users"`
+ UsersFile string `mapstructure:"users-file"`
+ CookieSecure bool `mapstructure:"cookie-secure"`
+ GithubClientId string `mapstructure:"github-client-id"`
+ GithubClientSecret string `mapstructure:"github-client-secret"`
+ GoogleClientId string `mapstructure:"google-client-id"`
+ GoogleClientSecret string `mapstructure:"google-client-secret"`
+ MicrosoftClientId string `mapstructure:"microsoft-client-id"`
+ MicrosoftClientSecret string `mapstructure:"microsoft-client-secret"`
}
type UserContext struct {
- Username string
+ Email string
IsLoggedIn bool
+ OAuth bool
+ Provider string
}
type APIConfig struct {
- Port int
- Address string
- Secret string
- AppURL string
+ Port int
+ Address string
+ Secret string
+ AppURL string
CookieSecure bool
-}
\ No newline at end of file
+}
+
+type OAuthConfig struct {
+ GithubClientId string
+ GithubClientSecret string
+ GoogleClientId string
+ GoogleClientSecret string
+ MicrosoftClientId string
+ MicrosoftClientSecret string
+}
+
+type OAuthBind struct {
+ Provider string `uri:"provider" binding:"required"`
+}
+
+type OAuthProviders struct {
+ Github *oauth.OAuth
+ Google *oauth.OAuth
+ Microsoft *oauth.OAuth
+}
+
+type OAuthLogin struct {
+ Email string
+ Token string
+}
diff --git a/internal/utils/utils.go b/internal/utils/utils.go
index e237301..608211f 100644
--- a/internal/utils/utils.go
+++ b/internal/utils/utils.go
@@ -22,7 +22,7 @@ func ParseUsers(users string) (types.Users, error) {
return types.Users{}, errors.New("invalid user format")
}
usersParsed = append(usersParsed, types.User{
- Username: userSplit[0],
+ Email: userSplit[0],
Password: userSplit[1],
})
}
diff --git a/site/src/pages/login-page.tsx b/site/src/pages/login-page.tsx
index d332f32..1a5d5f6 100644
--- a/site/src/pages/login-page.tsx
+++ b/site/src/pages/login-page.tsx
@@ -20,7 +20,7 @@ export const LoginPage = () => {
}
const schema = z.object({
- username: z.string(),
+ email: z.string().email(),
password: z.string(),
});
@@ -29,7 +29,7 @@ export const LoginPage = () => {
const form = useForm({
mode: "uncontrolled",
initialValues: {
- username: "",
+ email: "",
password: "",
},
validate: zodResolver(schema),
@@ -42,7 +42,7 @@ export const LoginPage = () => {
onError: () => {
notifications.show({
title: "Failed to login",
- message: "Check your username and password",
+ message: "Check your email and password",
color: "red",
});
},
@@ -68,12 +68,12 @@ export const LoginPage = () => {
diff --git a/site/src/pages/logout-page.tsx b/site/src/pages/logout-page.tsx
index 52a5e99..41c357a 100644
--- a/site/src/pages/logout-page.tsx
+++ b/site/src/pages/logout-page.tsx
@@ -5,9 +5,10 @@ import axios from "axios";
import { useUserContext } from "../context/user-context";
import { Navigate } from "react-router";
import { Layout } from "../components/layouts/layout";
+import { capitalize } from "../utils/utils";
export const LogoutPage = () => {
- const { isLoggedIn, username } = useUserContext();
+ const { isLoggedIn, email, oauth, provider } = useUserContext();
if (!isLoggedIn) {
return ;
@@ -43,8 +44,9 @@ export const LogoutPage = () => {
Logout
- You are currently logged in as {username}, click the
- button below to log out.
+ You are currently logged in as {email}{" "}
+ {oauth && `using ${capitalize(provider)}`}. Click the button below to
+ log out.