add user scripting with lua
This commit is contained in:
		
							parent
							
								
									5d2b82876d
								
							
						
					
					
						commit
						3920b8913d
					
				
							
								
								
									
										15
									
								
								config.go
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								config.go
									
									
									
									
									
								
							@ -19,6 +19,12 @@ type OAuthProvider struct {
 | 
				
			|||||||
	ClientID     string `toml:"client_id"`
 | 
						ClientID     string `toml:"client_id"`
 | 
				
			||||||
	ClientSecret string `toml:"client_secret"`
 | 
						ClientSecret string `toml:"client_secret"`
 | 
				
			||||||
	RedirectURL  string `toml:"redirect_url"`
 | 
						RedirectURL  string `toml:"redirect_url"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Only for custom OAuth provider
 | 
				
			||||||
 | 
						AuthURL  string   `toml:"auth_url"`
 | 
				
			||||||
 | 
						TokenURL string   `toml:"token_url"`
 | 
				
			||||||
 | 
						Scopes   []string `toml:"scopes"`
 | 
				
			||||||
 | 
						Script   string   `toml:"info_script"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Config struct {
 | 
					type Config struct {
 | 
				
			||||||
@ -50,6 +56,15 @@ func LoadConfig() (Config, oauth2.Config, error) {
 | 
				
			|||||||
	case "google":
 | 
						case "google":
 | 
				
			||||||
		oa2.Endpoint = endpoints.Google
 | 
							oa2.Endpoint = endpoints.Google
 | 
				
			||||||
		oa2.Scopes = []string{"https://www.googleapis.com/auth/userinfo.email"}
 | 
							oa2.Scopes = []string{"https://www.googleapis.com/auth/userinfo.email"}
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							oa2.Endpoint = oauth2.Endpoint{
 | 
				
			||||||
 | 
								AuthURL:  config.OAuthProvider.AuthURL,
 | 
				
			||||||
 | 
								TokenURL: config.OAuthProvider.TokenURL,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							oa2.Scopes = config.OAuthProvider.Scopes
 | 
				
			||||||
 | 
							if config.OAuthProvider.Script == "" {
 | 
				
			||||||
 | 
								panic("no script provided")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return config, oa2, err
 | 
						return config, oa2, err
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@ guarded_paths = ["/"]
 | 
				
			|||||||
# A list of all allowed users. For GitHub it is a list of usernames.
 | 
					# A list of all allowed users. For GitHub it is a list of usernames.
 | 
				
			||||||
# For Google it is a list of emails
 | 
					# For Google it is a list of emails
 | 
				
			||||||
allowed_users = ["lukasmwerner"]
 | 
					allowed_users = ["lukasmwerner"]
 | 
				
			||||||
 | 
					redirect_url = "http://localhost:3000/oauth/callback"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[upstream]
 | 
					[upstream]
 | 
				
			||||||
addr = "http://localhost:8080"
 | 
					addr = "http://localhost:8080"
 | 
				
			||||||
@ -11,7 +12,11 @@ program = "rezepte"
 | 
				
			|||||||
args = []
 | 
					args = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[provider]
 | 
					[provider]
 | 
				
			||||||
kind = "github" # can `google` or `github`
 | 
					kind = "OAuth2" # can `google` or `github` or custom via an undefined name
 | 
				
			||||||
 | 
					auth_url = "https://github.com/login/oauth/authorize"
 | 
				
			||||||
 | 
					token_url = "https://github.com/login/oauth/access_token"
 | 
				
			||||||
 | 
					scopes = ["read:user"]
 | 
				
			||||||
client_id = "<CHANGE_ME>"
 | 
					client_id = "<CHANGE_ME>"
 | 
				
			||||||
client_secret = "<CHANGE_ME>"
 | 
					client_secret = "<CHANGE_ME>"
 | 
				
			||||||
redirect_url = "http://localhost:3000/oauth/callback"
 | 
					# lua script that contains the function: `get_user_info(token)` and returns a string
 | 
				
			||||||
 | 
					info_script = "get_user_info.lua"
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										18
									
								
								config_simple.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								config_simple.toml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					listen_url = "http://localhost:3000"
 | 
				
			||||||
 | 
					guarded_paths = ["/"]
 | 
				
			||||||
 | 
					# A list of all allowed users. For GitHub it is a list of usernames.
 | 
				
			||||||
 | 
					# For Google it is a list of emails
 | 
				
			||||||
 | 
					allowed_users = ["lukasmwerner"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[upstream]
 | 
				
			||||||
 | 
					addr = "http://localhost:8080"
 | 
				
			||||||
 | 
					# An optional program to run as the upstream
 | 
				
			||||||
 | 
					program = "rezepte"
 | 
				
			||||||
 | 
					args = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[provider]
 | 
				
			||||||
 | 
					kind = "custom" # can `google` or `github` or `custom`
 | 
				
			||||||
 | 
					client_id = "<CHANGE_ME>"
 | 
				
			||||||
 | 
					client_secret = "<CHANGE_ME>"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					redirect_url = "http://localhost:3000/oauth/callback"
 | 
				
			||||||
							
								
								
									
										8
									
								
								get_user_info.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								get_user_info.lua
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					function get_user_info(token)
 | 
				
			||||||
 | 
						local json = require("json")
 | 
				
			||||||
 | 
						local resp, status = http.get("https://api.github.com/user", {
 | 
				
			||||||
 | 
							Authorization =  "Bearer " .. token
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						local data = json.decode(resp)
 | 
				
			||||||
 | 
						return data.login
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							@ -4,6 +4,8 @@ go 1.24.3
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	github.com/BurntSushi/toml v1.5.0
 | 
						github.com/BurntSushi/toml v1.5.0
 | 
				
			||||||
 | 
						github.com/Shopify/go-lua v0.0.0-20250605195627-15bbeb73041e
 | 
				
			||||||
 | 
						github.com/Shopify/goluago v0.0.0-20240527182001-ec4ec6c26eab
 | 
				
			||||||
	github.com/google/uuid v1.6.0
 | 
						github.com/google/uuid v1.6.0
 | 
				
			||||||
	golang.org/x/oauth2 v0.30.0
 | 
						golang.org/x/oauth2 v0.30.0
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										4
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.sum
									
									
									
									
									
								
							@ -1,5 +1,9 @@
 | 
				
			|||||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
 | 
					github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
 | 
				
			||||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
 | 
					github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
 | 
				
			||||||
 | 
					github.com/Shopify/go-lua v0.0.0-20250605195627-15bbeb73041e h1:zT/Iq/ow1l/J45IMajZ487dGbtjO9CfATa1O0T0aA9U=
 | 
				
			||||||
 | 
					github.com/Shopify/go-lua v0.0.0-20250605195627-15bbeb73041e/go.mod h1:M4CxjVc/1Nwka5atBv7G/sb7Ac2BDe3+FxbiT9iVNIQ=
 | 
				
			||||||
 | 
					github.com/Shopify/goluago v0.0.0-20240527182001-ec4ec6c26eab h1:lEd6vZgWJOjXAoIDUxSgg/U8/DbFEJnTfcBOQyAhej4=
 | 
				
			||||||
 | 
					github.com/Shopify/goluago v0.0.0-20240527182001-ec4ec6c26eab/go.mod h1:xIykgNzJggTWudqtySZwJa8Ab8NFgUSbSpPrTHQaHIc=
 | 
				
			||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 | 
					github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 | 
				
			||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
					github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
				
			||||||
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
 | 
					golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										6
									
								
								oauth.go
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								oauth.go
									
									
									
									
									
								
							@ -177,7 +177,7 @@ func (s *OAuthStore) CallbackHandler() http.Handler {
 | 
				
			|||||||
			http.Error(w, "Failed to exchange token", http.StatusInternalServerError)
 | 
								http.Error(w, "Failed to exchange token", http.StatusInternalServerError)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		userID, err := getUserInfo(s.config.OAuthProvider.Kind, tok.AccessToken)
 | 
							userID, err := getUserInfo(s.config.OAuthProvider.Kind, tok.AccessToken, s.config)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			http.Error(w, "Failed to get info", http.StatusInternalServerError)
 | 
								http.Error(w, "Failed to get info", http.StatusInternalServerError)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
@ -210,7 +210,7 @@ func (s *OAuthStore) CallbackHandler() http.Handler {
 | 
				
			|||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getUserInfo(providerKind, token string) (string, error) {
 | 
					func getUserInfo(providerKind, token string, c *Config) (string, error) {
 | 
				
			||||||
	switch providerKind {
 | 
						switch providerKind {
 | 
				
			||||||
	case "google":
 | 
						case "google":
 | 
				
			||||||
		type UserInfo struct {
 | 
							type UserInfo struct {
 | 
				
			||||||
@ -249,6 +249,6 @@ func getUserInfo(providerKind, token string) (string, error) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		return userInfo.Login, nil
 | 
							return userInfo.Login, nil
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		panic("unimplemented")
 | 
							return RunScript(c.OAuthProvider.Script, token)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										94
									
								
								scripts.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								scripts.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,94 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/Shopify/go-lua"
 | 
				
			||||||
 | 
						"github.com/Shopify/goluago/util"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var networkFunctions = []lua.RegistryFunction{
 | 
				
			||||||
 | 
						{Name: "get", Function: func(l *lua.State) int {
 | 
				
			||||||
 | 
							url := lua.CheckString(l, 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							req, err := http.NewRequest("GET", url, nil)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								lua.Errorf(l, "unable to build new request: %s", err.Error())
 | 
				
			||||||
 | 
								return 0
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if !l.IsNil(2) {
 | 
				
			||||||
 | 
								headers, err := util.PullStringTable(l, 2)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									lua.Errorf(l, "unable to acces headers table: %s", err.Error())
 | 
				
			||||||
 | 
									return 0
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								for key, value := range headers {
 | 
				
			||||||
 | 
									req.Header.Set(key, value)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							resp, err := http.DefaultClient.Do(req)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								lua.Errorf(l, "error fetching: %s", err.Error())
 | 
				
			||||||
 | 
								return 0
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							defer resp.Body.Close()
 | 
				
			||||||
 | 
							b, err := io.ReadAll(resp.Body)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								lua.Errorf(l, "error reading body: %s", err.Error())
 | 
				
			||||||
 | 
								return 0
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							l.PushString(string(b))
 | 
				
			||||||
 | 
							l.PushInteger(resp.StatusCode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return 2
 | 
				
			||||||
 | 
						}},
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var jsonFunctions = []lua.RegistryFunction{
 | 
				
			||||||
 | 
						{Name: "decode", Function: func(l *lua.State) int {
 | 
				
			||||||
 | 
							payload := lua.CheckString(l, 1)
 | 
				
			||||||
 | 
							var output any
 | 
				
			||||||
 | 
							if err := json.Unmarshal([]byte(payload), &output); err != nil {
 | 
				
			||||||
 | 
								lua.Errorf(l, "error parsing json: %s", err.Error())
 | 
				
			||||||
 | 
								return 0
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return util.DeepPush(l, output)
 | 
				
			||||||
 | 
						}},
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func RunScript(fileName string, token string) (string, error) {
 | 
				
			||||||
 | 
						l := lua.NewState()
 | 
				
			||||||
 | 
						lua.OpenLibraries(l)
 | 
				
			||||||
 | 
						lua.Require(l, "http", func(state *lua.State) int {
 | 
				
			||||||
 | 
							lua.NewLibrary(l, networkFunctions)
 | 
				
			||||||
 | 
							return 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}, true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						lua.Require(l, "json", func(state *lua.State) int {
 | 
				
			||||||
 | 
							lua.NewLibrary(l, jsonFunctions)
 | 
				
			||||||
 | 
							return 1
 | 
				
			||||||
 | 
						}, false)
 | 
				
			||||||
 | 
						err := lua.DoFile(l, fileName)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						l.Global("get_user_info")
 | 
				
			||||||
 | 
						l.PushString(token)
 | 
				
			||||||
 | 
						err = l.ProtectedCall(1, 1, 0)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						myStr, ok := l.ToString(-1)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return "", errors.New("unable to get function result as string")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return myStr, nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user