mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2025-10-28 04:35:40 +00:00
feat: generic oauth
This commit is contained in:
28
cmd/root.go
28
cmd/root.go
@@ -61,11 +61,17 @@ var rootCmd = &cobra.Command{
|
||||
|
||||
// Create OAuth config
|
||||
oauthConfig := types.OAuthConfig{
|
||||
GithubClientId: config.GithubClientId,
|
||||
GithubClientSecret: config.GithubClientSecret,
|
||||
GoogleClientId: config.GoogleClientId,
|
||||
GoogleClientSecret: config.GoogleClientSecret,
|
||||
AppURL: config.AppURL,
|
||||
GithubClientId: config.GithubClientId,
|
||||
GithubClientSecret: config.GithubClientSecret,
|
||||
GoogleClientId: config.GoogleClientId,
|
||||
GoogleClientSecret: config.GoogleClientSecret,
|
||||
GenericClientId: config.GenericClientId,
|
||||
GenericClientSecret: config.GenericClientSecret,
|
||||
GenericScopes: config.GenericScopes,
|
||||
GenericAuthURL: config.GenericAuthURL,
|
||||
GenericTokenURL: config.GenericTokenURL,
|
||||
GenericUserInfoURL: config.GenericUserInfoURL,
|
||||
AppURL: config.AppURL,
|
||||
}
|
||||
|
||||
// Create auth service
|
||||
@@ -127,6 +133,12 @@ func init() {
|
||||
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("generic-client-id", "", "Generic OAuth client ID.")
|
||||
rootCmd.Flags().String("generic-client-secret", "", "Generic OAuth client secret.")
|
||||
rootCmd.Flags().String("generic-scopes", "", "Generic OAuth scopes.")
|
||||
rootCmd.Flags().String("generic-auth-url", "", "Generic OAuth auth URL.")
|
||||
rootCmd.Flags().String("generic-token-url", "", "Generic OAuth token URL.")
|
||||
rootCmd.Flags().String("generic-user-info-url", "", "Generic OAuth user info URL.")
|
||||
viper.BindEnv("port", "PORT")
|
||||
viper.BindEnv("address", "ADDRESS")
|
||||
viper.BindEnv("secret", "SECRET")
|
||||
@@ -138,5 +150,11 @@ func init() {
|
||||
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("generic-client-id", "GENERIC_CLIENT_ID")
|
||||
viper.BindEnv("generic-client-secret", "GENERIC_CLIENT_SECRET")
|
||||
viper.BindEnv("generic-scopes", "GENERIC_SCOPES")
|
||||
viper.BindEnv("generic-auth-url", "GENERIC_AUTH_URL")
|
||||
viper.BindEnv("generic-token-url", "GENERIC_TOKEN_URL")
|
||||
viper.BindEnv("generic-user-info-url", "GENERIC_USER_INFO_URL")
|
||||
viper.BindPFlags(rootCmd.Flags())
|
||||
}
|
||||
|
||||
35
internal/providers/generic.go
Normal file
35
internal/providers/generic.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package providers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type GenericUserInfoResponse struct {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
func GetGenericEmail(client *http.Client, url string) (string, error) {
|
||||
res, resErr := client.Get(url)
|
||||
|
||||
if resErr != nil {
|
||||
return "", resErr
|
||||
}
|
||||
|
||||
body, bodyErr := io.ReadAll(res.Body)
|
||||
|
||||
if bodyErr != nil {
|
||||
return "", bodyErr
|
||||
}
|
||||
|
||||
var user GenericUserInfoResponse
|
||||
|
||||
jsonErr := json.Unmarshal(body, &user)
|
||||
|
||||
if jsonErr != nil {
|
||||
return "", jsonErr
|
||||
}
|
||||
|
||||
return user.Email, nil
|
||||
}
|
||||
@@ -7,12 +7,12 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type GithubEmailsResponse []struct {
|
||||
Email string `json:"email"`
|
||||
Primary bool `json:"primary"`
|
||||
type GithubUserInfoResponse []struct {
|
||||
Email string `json:"email"`
|
||||
Primary bool `json:"primary"`
|
||||
}
|
||||
|
||||
func GithubScopes() ([]string) {
|
||||
func GithubScopes() []string {
|
||||
return []string{"user:email"}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func GetGithubEmail(client *http.Client) (string, error) {
|
||||
return "", bodyErr
|
||||
}
|
||||
|
||||
var emails GithubEmailsResponse
|
||||
var emails GithubUserInfoResponse
|
||||
|
||||
jsonErr := json.Unmarshal(body, &emails)
|
||||
|
||||
@@ -44,4 +44,4 @@ func GetGithubEmail(client *http.Client) (string, error) {
|
||||
}
|
||||
|
||||
return "", errors.New("no primary email found")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type GoogleUserinfoResponse struct {
|
||||
type GoogleUserInfoResponse struct {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func GetGoogleEmail(client *http.Client) (string, error) {
|
||||
return "", bodyErr
|
||||
}
|
||||
|
||||
var user GoogleUserinfoResponse
|
||||
var user GoogleUserInfoResponse
|
||||
|
||||
jsonErr := json.Unmarshal(body, &user)
|
||||
|
||||
|
||||
@@ -17,10 +17,10 @@ func NewProviders(config types.OAuthConfig) *Providers {
|
||||
}
|
||||
|
||||
type Providers struct {
|
||||
Config types.OAuthConfig
|
||||
Github *oauth.OAuth
|
||||
Google *oauth.OAuth
|
||||
Microsoft *oauth.OAuth
|
||||
Config types.OAuthConfig
|
||||
Github *oauth.OAuth
|
||||
Google *oauth.OAuth
|
||||
Generic *oauth.OAuth
|
||||
}
|
||||
|
||||
func (providers *Providers) Init() {
|
||||
@@ -46,6 +46,20 @@ func (providers *Providers) Init() {
|
||||
})
|
||||
providers.Google.Init()
|
||||
}
|
||||
if providers.Config.GenericClientId != "" && providers.Config.GenericClientSecret != "" {
|
||||
log.Info().Msg("Initializing Generic OAuth")
|
||||
providers.Generic = oauth.NewOAuth(oauth2.Config{
|
||||
ClientID: providers.Config.GenericClientId,
|
||||
ClientSecret: providers.Config.GenericClientSecret,
|
||||
RedirectURL: fmt.Sprintf("%s/api/oauth/callback/generic", providers.Config.AppURL),
|
||||
Scopes: []string{providers.Config.GenericScopes},
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: providers.Config.GenericAuthURL,
|
||||
TokenURL: providers.Config.GenericTokenURL,
|
||||
},
|
||||
})
|
||||
providers.Generic.Init()
|
||||
}
|
||||
}
|
||||
|
||||
func (providers *Providers) GetProvider(provider string) *oauth.OAuth {
|
||||
@@ -54,6 +68,8 @@ func (providers *Providers) GetProvider(provider string) *oauth.OAuth {
|
||||
return providers.Github
|
||||
case "google":
|
||||
return providers.Google
|
||||
case "generic":
|
||||
return providers.Generic
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
@@ -81,6 +97,16 @@ func (providers *Providers) GetUser(provider string) (string, error) {
|
||||
return "", emailErr
|
||||
}
|
||||
return email, nil
|
||||
case "generic":
|
||||
if providers.Generic == nil {
|
||||
return "", nil
|
||||
}
|
||||
client := providers.Generic.GetClient()
|
||||
email, emailErr := GetGenericEmail(client, providers.Config.GenericUserInfoURL)
|
||||
if emailErr != nil {
|
||||
return "", emailErr
|
||||
}
|
||||
return email, nil
|
||||
default:
|
||||
return "", nil
|
||||
}
|
||||
@@ -94,5 +120,8 @@ func (provider *Providers) GetConfiguredProviders() []string {
|
||||
if provider.Google != nil {
|
||||
providers = append(providers, "google")
|
||||
}
|
||||
if provider.Generic != nil {
|
||||
providers = append(providers, "generic")
|
||||
}
|
||||
return providers
|
||||
}
|
||||
|
||||
@@ -19,17 +19,23 @@ type User struct {
|
||||
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"`
|
||||
GithubClientId string `mapstructure:"github-client-id"`
|
||||
GithubClientSecret string `mapstructure:"github-client-secret"`
|
||||
GoogleClientId string `mapstructure:"google-client-id"`
|
||||
GoogleClientSecret string `mapstructure:"google-client-secret"`
|
||||
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"`
|
||||
GenericClientId string `mapstructure:"generic-client-id"`
|
||||
GenericClientSecret string `mapstructure:"generic-client-secret"`
|
||||
GenericScopes string `mapstructure:"generic-scopes"`
|
||||
GenericAuthURL string `mapstructure:"generic-auth-url"`
|
||||
GenericTokenURL string `mapstructure:"generic-token-url"`
|
||||
GenericUserInfoURL string `mapstructure:"generic-user-info-url"`
|
||||
}
|
||||
|
||||
type UserContext struct {
|
||||
@@ -48,11 +54,17 @@ type APIConfig struct {
|
||||
}
|
||||
|
||||
type OAuthConfig struct {
|
||||
GithubClientId string
|
||||
GithubClientSecret string
|
||||
GoogleClientId string
|
||||
GoogleClientSecret string
|
||||
AppURL string
|
||||
GithubClientId string
|
||||
GithubClientSecret string
|
||||
GoogleClientId string
|
||||
GoogleClientSecret string
|
||||
GenericClientId string
|
||||
GenericClientSecret string
|
||||
GenericScopes string
|
||||
GenericAuthURL string
|
||||
GenericTokenURL string
|
||||
GenericUserInfoURL string
|
||||
AppURL string
|
||||
}
|
||||
|
||||
type OAuthRequest struct {
|
||||
|
||||
24
site/src/icons/oauth.tsx
Normal file
24
site/src/icons/oauth.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { SVGProps } from "react";
|
||||
|
||||
export function OAuthIcon(props: SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 24 24"
|
||||
{...props}
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
>
|
||||
<path d="M2 12a10 10 0 1 0 20 0a10 10 0 1 0-20 0"></path>
|
||||
<path d="M12.556 6c.65 0 1.235.373 1.508.947l2.839 7.848a1.646 1.646 0 0 1-1.01 2.108a1.673 1.673 0 0 1-2.068-.851L13.365 15h-2.73l-.398.905A1.67 1.67 0 0 1 8.26 16.95l-.153-.047a1.647 1.647 0 0 1-1.056-1.956l2.824-7.852a1.66 1.66 0 0 1 1.409-1.087z"></path>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import { Navigate } from "react-router";
|
||||
import { Layout } from "../components/layouts/layout";
|
||||
import { GoogleIcon } from "../icons/google";
|
||||
import { GithubIcon } from "../icons/github";
|
||||
import { OAuthIcon } from "../icons/oauth";
|
||||
|
||||
export const LoginPage = () => {
|
||||
const queryString = window.location.search;
|
||||
@@ -125,6 +126,19 @@ export const LoginPage = () => {
|
||||
</Button>
|
||||
</Grid.Col>
|
||||
)}
|
||||
{configuredProviders.includes("generic") && (
|
||||
<Grid.Col span="content">
|
||||
<Button
|
||||
radius="xl"
|
||||
leftSection={<OAuthIcon style={{ width: 14, height: 14 }} />}
|
||||
variant="default"
|
||||
onClick={() => loginOAuthMutation.mutate("generic")}
|
||||
loading={loginOAuthMutation.isLoading}
|
||||
>
|
||||
Generic
|
||||
</Button>
|
||||
</Grid.Col>
|
||||
)}
|
||||
</Grid>
|
||||
<Divider
|
||||
label="Or continue with email"
|
||||
|
||||
Reference in New Issue
Block a user