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 = () => {
{ type="submit" loading={loginMutation.isLoading} > - Sign in + Login
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.