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
|
// Create OAuth config
|
||||||
oauthConfig := types.OAuthConfig{
|
oauthConfig := types.OAuthConfig{
|
||||||
GithubClientId: config.GithubClientId,
|
GithubClientId: config.GithubClientId,
|
||||||
GithubClientSecret: config.GithubClientSecret,
|
GithubClientSecret: config.GithubClientSecret,
|
||||||
GoogleClientId: config.GoogleClientId,
|
GoogleClientId: config.GoogleClientId,
|
||||||
GoogleClientSecret: config.GoogleClientSecret,
|
GoogleClientSecret: config.GoogleClientSecret,
|
||||||
AppURL: config.AppURL,
|
GenericClientId: config.GenericClientId,
|
||||||
|
GenericClientSecret: config.GenericClientSecret,
|
||||||
|
GenericScopes: config.GenericScopes,
|
||||||
|
GenericAuthURL: config.GenericAuthURL,
|
||||||
|
GenericTokenURL: config.GenericTokenURL,
|
||||||
|
GenericUserInfoURL: config.GenericUserInfoURL,
|
||||||
|
AppURL: config.AppURL,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create auth service
|
// Create auth service
|
||||||
@@ -127,6 +133,12 @@ func init() {
|
|||||||
rootCmd.Flags().String("github-client-secret", "", "Github OAuth client secret.")
|
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-id", "", "Google OAuth client ID.")
|
||||||
rootCmd.Flags().String("google-client-secret", "", "Google OAuth client secret.")
|
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("port", "PORT")
|
||||||
viper.BindEnv("address", "ADDRESS")
|
viper.BindEnv("address", "ADDRESS")
|
||||||
viper.BindEnv("secret", "SECRET")
|
viper.BindEnv("secret", "SECRET")
|
||||||
@@ -138,5 +150,11 @@ func init() {
|
|||||||
viper.BindEnv("github-client-secret", "GITHUB_CLIENT_SECRET")
|
viper.BindEnv("github-client-secret", "GITHUB_CLIENT_SECRET")
|
||||||
viper.BindEnv("google-client-id", "GOOGLE_CLIENT_ID")
|
viper.BindEnv("google-client-id", "GOOGLE_CLIENT_ID")
|
||||||
viper.BindEnv("google-client-secret", "GOOGLE_CLIENT_SECRET")
|
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())
|
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"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GithubEmailsResponse []struct {
|
type GithubUserInfoResponse []struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Primary bool `json:"primary"`
|
Primary bool `json:"primary"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GithubScopes() ([]string) {
|
func GithubScopes() []string {
|
||||||
return []string{"user:email"}
|
return []string{"user:email"}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ func GetGithubEmail(client *http.Client) (string, error) {
|
|||||||
return "", bodyErr
|
return "", bodyErr
|
||||||
}
|
}
|
||||||
|
|
||||||
var emails GithubEmailsResponse
|
var emails GithubUserInfoResponse
|
||||||
|
|
||||||
jsonErr := json.Unmarshal(body, &emails)
|
jsonErr := json.Unmarshal(body, &emails)
|
||||||
|
|
||||||
@@ -44,4 +44,4 @@ func GetGithubEmail(client *http.Client) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return "", errors.New("no primary email found")
|
return "", errors.New("no primary email found")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GoogleUserinfoResponse struct {
|
type GoogleUserInfoResponse struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ func GetGoogleEmail(client *http.Client) (string, error) {
|
|||||||
return "", bodyErr
|
return "", bodyErr
|
||||||
}
|
}
|
||||||
|
|
||||||
var user GoogleUserinfoResponse
|
var user GoogleUserInfoResponse
|
||||||
|
|
||||||
jsonErr := json.Unmarshal(body, &user)
|
jsonErr := json.Unmarshal(body, &user)
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ func NewProviders(config types.OAuthConfig) *Providers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Providers struct {
|
type Providers struct {
|
||||||
Config types.OAuthConfig
|
Config types.OAuthConfig
|
||||||
Github *oauth.OAuth
|
Github *oauth.OAuth
|
||||||
Google *oauth.OAuth
|
Google *oauth.OAuth
|
||||||
Microsoft *oauth.OAuth
|
Generic *oauth.OAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
func (providers *Providers) Init() {
|
func (providers *Providers) Init() {
|
||||||
@@ -46,6 +46,20 @@ func (providers *Providers) Init() {
|
|||||||
})
|
})
|
||||||
providers.Google.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 {
|
func (providers *Providers) GetProvider(provider string) *oauth.OAuth {
|
||||||
@@ -54,6 +68,8 @@ func (providers *Providers) GetProvider(provider string) *oauth.OAuth {
|
|||||||
return providers.Github
|
return providers.Github
|
||||||
case "google":
|
case "google":
|
||||||
return providers.Google
|
return providers.Google
|
||||||
|
case "generic":
|
||||||
|
return providers.Generic
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -81,6 +97,16 @@ func (providers *Providers) GetUser(provider string) (string, error) {
|
|||||||
return "", emailErr
|
return "", emailErr
|
||||||
}
|
}
|
||||||
return email, nil
|
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:
|
default:
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
@@ -94,5 +120,8 @@ func (provider *Providers) GetConfiguredProviders() []string {
|
|||||||
if provider.Google != nil {
|
if provider.Google != nil {
|
||||||
providers = append(providers, "google")
|
providers = append(providers, "google")
|
||||||
}
|
}
|
||||||
|
if provider.Generic != nil {
|
||||||
|
providers = append(providers, "generic")
|
||||||
|
}
|
||||||
return providers
|
return providers
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,17 +19,23 @@ type User struct {
|
|||||||
type Users []User
|
type Users []User
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Port int `validate:"number" mapstructure:"port"`
|
Port int `validate:"number" mapstructure:"port"`
|
||||||
Address string `mapstructure:"address, ip4_addr"`
|
Address string `mapstructure:"address, ip4_addr"`
|
||||||
Secret string `validate:"required,len=32" mapstructure:"secret"`
|
Secret string `validate:"required,len=32" mapstructure:"secret"`
|
||||||
AppURL string `validate:"required,url" mapstructure:"app-url"`
|
AppURL string `validate:"required,url" mapstructure:"app-url"`
|
||||||
Users string `mapstructure:"users"`
|
Users string `mapstructure:"users"`
|
||||||
UsersFile string `mapstructure:"users-file"`
|
UsersFile string `mapstructure:"users-file"`
|
||||||
CookieSecure bool `mapstructure:"cookie-secure"`
|
CookieSecure bool `mapstructure:"cookie-secure"`
|
||||||
GithubClientId string `mapstructure:"github-client-id"`
|
GithubClientId string `mapstructure:"github-client-id"`
|
||||||
GithubClientSecret string `mapstructure:"github-client-secret"`
|
GithubClientSecret string `mapstructure:"github-client-secret"`
|
||||||
GoogleClientId string `mapstructure:"google-client-id"`
|
GoogleClientId string `mapstructure:"google-client-id"`
|
||||||
GoogleClientSecret string `mapstructure:"google-client-secret"`
|
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 {
|
type UserContext struct {
|
||||||
@@ -48,11 +54,17 @@ type APIConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type OAuthConfig struct {
|
type OAuthConfig struct {
|
||||||
GithubClientId string
|
GithubClientId string
|
||||||
GithubClientSecret string
|
GithubClientSecret string
|
||||||
GoogleClientId string
|
GoogleClientId string
|
||||||
GoogleClientSecret string
|
GoogleClientSecret string
|
||||||
AppURL string
|
GenericClientId string
|
||||||
|
GenericClientSecret string
|
||||||
|
GenericScopes string
|
||||||
|
GenericAuthURL string
|
||||||
|
GenericTokenURL string
|
||||||
|
GenericUserInfoURL string
|
||||||
|
AppURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
type OAuthRequest struct {
|
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 { Layout } from "../components/layouts/layout";
|
||||||
import { GoogleIcon } from "../icons/google";
|
import { GoogleIcon } from "../icons/google";
|
||||||
import { GithubIcon } from "../icons/github";
|
import { GithubIcon } from "../icons/github";
|
||||||
|
import { OAuthIcon } from "../icons/oauth";
|
||||||
|
|
||||||
export const LoginPage = () => {
|
export const LoginPage = () => {
|
||||||
const queryString = window.location.search;
|
const queryString = window.location.search;
|
||||||
@@ -125,6 +126,19 @@ export const LoginPage = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Grid.Col>
|
</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>
|
</Grid>
|
||||||
<Divider
|
<Divider
|
||||||
label="Or continue with email"
|
label="Or continue with email"
|
||||||
|
|||||||
Reference in New Issue
Block a user