diff --git a/.gitignore b/.gitignore
index 5ce2595..63276d7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -148,7 +148,6 @@ dist
.pnp.*
torima
-proxy
sqlite3.db
test.sqlite3
secret.env
@@ -157,7 +156,8 @@ private.der
.gitignore
cert.der
-example/main
+serv/serv
*.db
tmp
+
diff --git a/README.md b/README.md
index 6df1090..38317e9 100644
--- a/README.md
+++ b/README.md
@@ -100,7 +100,7 @@ default_origin: app:5000 # your front-end server
protection_scope:
- api:5001 # your API servers
-white_list_path:
+skip_auth_list:
- /favicon.ico
scheme: http
diff --git a/config.yaml b/config.yaml
index b3b800d..86927c1 100644
--- a/config.yaml
+++ b/config.yaml
@@ -1,6 +1,6 @@
port: 8080
-white_list_path:
+skip_auth_list:
- /favicon.ico
# default_origin: 127.0.0.1:8080
diff --git a/core/config.go b/core/config.go
index 15e8954..23027cb 100644
--- a/core/config.go
+++ b/core/config.go
@@ -1,8 +1,6 @@
package core
import (
- "fmt"
- "log"
"os"
"github.com/creasty/defaults"
@@ -14,12 +12,12 @@ type TorimaConfig struct {
Host string `yaml:"host" default:"http://127.0.0.1:8080"`
Port int `yaml:"port" default:"8080" `
Scheme string `yaml:"scheme" default:"http"`
- WhiteListPath []string `yaml:"white_list_path" default:"[]"`
+ SkipAuthList []string `yaml:"skip_auth_list" default:"[]"`
ProtectionScope []string `yaml:"protection_scope" default:"[]"`
WebRoot string `yaml:"web_root" default:"/torima"`
}
-func readConfig() (*TorimaConfig, error) {
+func ReadConfig() (*TorimaConfig, error) {
var m TorimaConfig
var def TorimaConfig // default config
@@ -44,34 +42,3 @@ func readConfig() (*TorimaConfig, error) {
return &m, err
}
-
-func printConfig(config *TorimaConfig) {
- fmt.Println("default_origin:", config.DefaultOrigin)
- fmt.Println("host:", config.Host)
- fmt.Println("port:", config.Port)
- fmt.Println("scheme:", config.Scheme)
- fmt.Println("white_list_path:", config.WhiteListPath)
- fmt.Println("protection_scope:", config.ProtectionScope)
- fmt.Println("web_root:", config.WebRoot)
-}
-
-func readEnv(name, def string) string {
- value := os.Getenv(name)
-
- if value == "" {
- fmt.Printf("environment variable '%v' is not found so that proxy use '%v'\n", name, def)
- value = def
- }
-
- return value
-}
-
-func readEnvOrPanic(name string) string {
- value := os.Getenv(name)
-
- if value == "" {
- log.Fatalf("environment variable '%v' is not found", name)
- }
-
- return value
-}
diff --git a/core/core.go b/core/core.go
index 266978e..bd53c56 100644
--- a/core/core.go
+++ b/core/core.go
@@ -7,13 +7,38 @@ import (
"github.com/gin-gonic/gin"
)
-type TorimaDirector = func(proxy *TorimaProxy, req *http.Request, c *gin.Context) (bool, error)
-type TorimaModifyResponse = func(proxy *TorimaProxy, req *http.Response, c *gin.Context) (bool, error)
+type TorimaPackageStatus = int
+
+const (
+ AuthNeeded TorimaPackageStatus = iota
+ Authed
+ NoAuthNeeded
+ ForceStop
+ Keep
+)
+
+type TorimaPackageTarget interface{ *http.Request | *http.Response }
+
+type TorimaPackageContext[T TorimaPackageTarget] struct {
+ Proxy *TorimaProxy
+ Target T
+ GinContext *gin.Context
+ PackageStatus TorimaPackageStatus
+}
+
+type TorimaDirectorPackageContext = TorimaPackageContext[*http.Request]
+type TorimaModifyResponsePackageContext = TorimaPackageContext[*http.Response]
+
+type TorimaDirector func(*TorimaDirectorPackageContext) (TorimaPackageStatus, error)
+type TorimaModifyResponse func(*TorimaModifyResponsePackageContext) (TorimaPackageStatus, error)
+type TorimaDirectors []func(*TorimaDirectorPackageContext) (TorimaPackageStatus, error)
+type TorimaModifyResponses []func(*TorimaModifyResponsePackageContext) (TorimaPackageStatus, error)
+
type TorimaProxyWebPage = func(proxy *TorimaProxy, c *gin.RouterGroup)
type TorimaProxy struct {
- Directors []TorimaDirector
- ModifyResponses []TorimaModifyResponse
+ Directors TorimaDirectors
+ ModifyResponses TorimaModifyResponses
ProxyWebPages []TorimaProxyWebPage
Engine *gin.Engine
Database *Database
@@ -24,8 +49,8 @@ type TorimaProxy struct {
func NewOchancoProxy(
r *gin.Engine,
- directors []TorimaDirector,
- modifyResponses []TorimaModifyResponse,
+ directors TorimaDirectors,
+ modifyResponses TorimaModifyResponses,
proxyWebPages []TorimaProxyWebPage,
config *TorimaConfig,
database *Database,
diff --git a/core/director.go b/core/director.go
deleted file mode 100644
index caacbe1..0000000
--- a/core/director.go
+++ /dev/null
@@ -1,149 +0,0 @@
-package core
-
-import (
- "bytes"
- "fmt"
- "net/http"
- "net/http/httputil"
- "strings"
-
- "github.com/ochanoco/ninsho"
- gin_ninsho "github.com/ochanoco/ninsho/extension/gin"
-
- "github.com/gin-gonic/gin"
- "golang.org/x/exp/slices"
-)
-
-func RouteDirector(host string, proxy *TorimaProxy, req *http.Request, c *gin.Context) (bool, error) {
- req.URL.Host = host
-
- // just to be sure
- req.Header.Del("X-Torima-Proxy-Token")
- req.Header.Set("X-Torima-Proxy-Token", SECRET)
-
- req.URL.Scheme = proxy.Config.Scheme
-
- return CONTINUE, nil
-}
-
-func DefaultRouteDirector(proxy *TorimaProxy, req *http.Request, c *gin.Context) (bool, error) {
- if strings.HasPrefix(req.URL.Path, "/torima/") {
- return CONTINUE, nil
- }
-
- host := proxy.Config.DefaultOrigin
-
- if host == "" {
- err := fmt.Errorf("failed to get destination config (%s)", host)
- return FINISHED, err
- }
-
- return RouteDirector(host, proxy, req, c)
-}
-
-func ThirdPartyDirector(proxy *TorimaProxy, req *http.Request, c *gin.Context) (bool, error) {
- path := strings.Split(req.URL.Path, "/")
- hasRedirectPrefix := strings.HasPrefix(req.URL.Path, "/torima/redirect/")
-
- if !hasRedirectPrefix || len(path) < 3 {
- return CONTINUE, nil
- }
-
- for _, origin := range proxy.Config.ProtectionScope {
- if origin == path[3] {
- req.Host = origin
- req.URL.Host = origin
-
- p := strings.Join(path[4:], "/")
- req.URL.Path = "/" + p
-
- req.URL.Scheme = "https"
- return RouteDirector(origin, proxy, req, c)
- }
- }
-
- return CONTINUE, nil
-}
-
-func SanitizeHeaderDirector(proxy *TorimaProxy, req *http.Request, c *gin.Context) (bool, error) {
- headers := http.Header{
- "Host": {proxy.Config.Host},
- "User-Agent": {"torima"},
-
- "Content-Type": req.Header["Content-Type"],
- "Content-Length": req.Header["Content-Length"],
-
- "Accept": req.Header["Accept"],
- "Connection": req.Header["Connection"],
-
- "Accept-Encoding": req.Header["Accept-Encoding"],
- "Accept-Language": req.Header["Accept-Language"],
-
- "Cookie": req.Header["Cookie"],
- }
-
- req.Header = headers
-
- return CONTINUE, nil
-
-}
-
-func AuthDirector(proxy *TorimaProxy, req *http.Request, c *gin.Context) (bool, error) {
- user, err := gin_ninsho.LoadUser[ninsho.LINE_USER](c)
-
- // just to be sure
- req.Header.Del("X-Torima-UserID")
-
- if err != nil {
- err = makeError(err, "failed to get user from session: ")
- return FINISHED, err
- }
-
- if user != nil {
- req.Header.Set("X-Torima-UserID", user.Sub)
- return CONTINUE, nil
- }
-
- if req.Method == "GET" && req.URL.RawQuery == "" {
- if req.URL.Path == "/" {
- return CONTINUE, nil
- }
-
- if slices.Contains(proxy.Config.WhiteListPath, req.URL.Path) {
- return CONTINUE, nil
- }
- }
-
- return FINISHED, makeError(fmt.Errorf(""), unauthorizedErrorTag)
-}
-
-func MakeLogDirector(flag string) TorimaDirector {
- return func(proxy *TorimaProxy, req *http.Request, c *gin.Context) (bool, error) {
- request, err := httputil.DumpRequest(req, true)
-
- if err != nil {
- err = makeError(err, "failed to dump headers to json: ")
- return FINISHED, err
- }
-
- splited := bytes.Split(request, []byte("\r\n\r\n"))
-
- header := splited[0]
- headerLen := len(header)
-
- body := request[headerLen:]
-
- l := proxy.Database.CreateRequestLog(string(header), body, flag)
- _, err = l.Save(proxy.Database.Ctx)
-
- if err != nil {
- err = makeError(err, "failed to save request: ")
- return FINISHED, err
- }
-
- return CONTINUE, err
- }
-}
-
-var BeforeLogDirector = MakeLogDirector("before")
-var AfterLogDirector = MakeLogDirector("after")
diff --git a/core/director_test.go b/core/director_test.go
deleted file mode 100644
index bfd77be..0000000
--- a/core/director_test.go
+++ /dev/null
@@ -1,239 +0,0 @@
-package core
-
-import (
- "encoding/json"
- "fmt"
- "net/http"
- "net/http/httptest"
- "net/url"
- "os"
- "testing"
-
- "github.com/gin-contrib/sessions"
- "github.com/gin-contrib/sessions/cookie"
- "github.com/gin-gonic/gin"
- "github.com/ochanoco/ninsho"
- "github.com/stretchr/testify/assert"
-)
-
-func directorSample(t *testing.T) (*http.Request, *TestResponseRecorder, *gin.Context, *TorimaProxy) {
- DB_TYPE = "sqlite3"
- DB_CONFIG = "../data/test.db?_fk=1"
- SECRET = "test_secret"
-
- recorder := CreateTestResponseRecorder()
- context, r := gin.CreateTestContext(recorder)
-
- store := cookie.NewStore([]byte("test"))
- r.Use(sessions.Sessions("torima-session", store))
-
- db, err := InitDB(DB_CONFIG)
- assert.NoError(t, err)
-
- config, file, err := readTestConfig(t)
- assert.NoError(t, err)
- defer os.Remove(file.Name())
-
- proxy := NewOchancoProxy(r, DEFAULT_DIRECTORS, DEFAULT_MODIFY_RESPONSES, DEFAULT_PROXYWEB_PAGES, config, db)
- req := httptest.NewRequest("GET", "http://localhost:8080/", nil)
-
- return req, recorder, context, &proxy
-}
-
-func setupMockServer(handler http.HandlerFunc, req *http.Request, t *testing.T) (*httptest.Server, *url.URL) {
- h := http.HandlerFunc(handler)
-
- ts := httptest.NewServer(h)
- u, err := url.Parse(ts.URL)
- assert.NoError(t, err)
-
- req.URL.Path = "/hello"
- req.URL.Host = u.Host
- req.Host = u.Host
-
- return ts, u
-}
-
-// test for RouteDirector
-func TestRouteDirector(t *testing.T) {
- req, _, context, proxy := directorSample(t)
- c, err := RouteDirector("example.com", proxy, req, context)
-
- assert.NoError(t, err)
- assert.Equal(t, CONTINUE, c)
- assert.Equal(t, "example.com", req.URL.Host)
- assert.Equal(t, "http", req.URL.Scheme)
- assert.Equal(t, SECRET, req.Header.Get("X-Torima-Proxy-Token"))
-}
-
-// test for DefaultRouteDirector
-func TestThirdPartyDirector(t *testing.T) {
- h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintln(w, "Hello, client")
- })
-
- ts := httptest.NewServer(h)
- defer ts.Close()
-
- u, err := url.Parse(ts.URL)
- assert.NoError(t, err)
-
- host := fmt.Sprintf("%v:%v", u.Host, u.Port())
-
- req, _, context, proxy := directorSample(t)
-
- req.URL.Path = "/torima/redirect/" + host
-
- proxy.Config.ProtectionScope = []string{host}
-
- c, err := ThirdPartyDirector(proxy, req, context)
- assert.NoError(t, err)
- assert.Equal(t, CONTINUE, c)
-
- c, err = ThirdPartyDirector(proxy, req, context)
- assert.NoError(t, err)
-
- assert.Equal(t, CONTINUE, c)
- assert.Equal(t, host, req.URL.Host)
-}
-
-// test for DefaultRouteDirector
-func TestThirdPartyDirectorNoParmit(t *testing.T) {
- unpermitHost := "not-in-list.example.com"
-
- req, _, context, proxy := directorSample(t)
-
- req.URL.Path = "/torima/redirect/" + unpermitHost + "/"
-
- c, err := ThirdPartyDirector(proxy, req, context)
- assert.NoError(t, err)
- assert.Equal(t, CONTINUE, c)
-
- c, err = ThirdPartyDirector(proxy, req, context)
- assert.NoError(t, err)
-
- assert.Equal(t, CONTINUE, c)
- assert.NotEqual(t, unpermitHost, req.URL.Host)
-}
-
-// test for AuthDirector
-func TestAuthDirector(t *testing.T) {
- h := func(w http.ResponseWriter, r *http.Request) {
- assert.Equal(t, "1", r.Header.Get("X-Torima-UserID"))
- fmt.Fprintln(w, "Hello, client")
- }
-
- testDirector := func(proxy *TorimaProxy, req *http.Request, context *gin.Context) (bool, error) {
- session := sessions.Default(context)
-
- user := ninsho.LINE_USER{
- Sub: "1",
- }
- json, _ := json.Marshal(user)
-
- session.Set("user", string(json))
- err := session.Save()
- assert.NoError(t, err)
-
- c, err := AuthDirector(proxy, req, context)
-
- assert.NoError(t, err)
- assert.Equal(t, CONTINUE, c)
-
- return CONTINUE, nil
- }
-
- DEFAULT_DIRECTORS = []TorimaDirector{
- testDirector,
- }
-
- req, recorder, _, proxy := directorSample(t)
- mockServer, _ := setupMockServer(h, req, t)
- defer mockServer.Close()
-
- req.URL.Path = "/hello?hoge"
-
- proxy.Engine.ServeHTTP(recorder, req)
- assert.Equal(t, http.StatusOK, recorder.Result().StatusCode)
-}
-
-func TestAuthDirectorWithWhiteList(t *testing.T) {
- h := func(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintln(w, "Hello, client")
- }
-
- DEFAULT_DIRECTORS = []TorimaDirector{
- AuthDirector,
- }
-
- req, recorder, _, proxy := directorSample(t)
- mockServer, _ := setupMockServer(h, req, t)
- defer mockServer.Close()
-
- proxy.Config.WhiteListPath = []string{
- "/hello",
- }
-
- proxy.Engine.ServeHTTP(recorder, req)
- assert.Equal(t, http.StatusOK, recorder.Result().StatusCode)
-}
-
-// test for AuthDirector
-func TestAuthDirectorNoPermit(t *testing.T) {
- DEFAULT_DIRECTORS = []TorimaDirector{
- AuthDirector,
- }
-
- req, recorder, _, proxy := directorSample(t)
- req.URL.Path = "/hello"
-
- proxy.Engine.ServeHTTP(recorder, req)
- assert.Equal(t, http.StatusUnauthorized, recorder.Result().StatusCode)
-}
-
-type TestResponseRecorder struct {
- *httptest.ResponseRecorder
- closeChannel chan bool
-}
-
-func (r *TestResponseRecorder) CloseNotify() <-chan bool {
- return r.closeChannel
-}
-
-func (r *TestResponseRecorder) closeClient() {
- r.closeChannel <- true
-}
-
-func CreateTestResponseRecorder() *TestResponseRecorder {
- return &TestResponseRecorder{
- httptest.NewRecorder(),
- make(chan bool, 1),
- }
-}
-
-func TestLogDirector(t *testing.T) {
- req, recorder, context, proxy := directorSample(t)
-
- before, err := proxy.Database.Client.RequestLog.Query().Count(proxy.Database.Ctx)
- assert.NoError(t, err)
-
- req.URL.Path = "/"
-
- BeforeLogDirector(proxy, req, context)
-
- assert.Equal(t, http.StatusOK, recorder.Result().StatusCode)
-
- after, err := proxy.Database.Client.RequestLog.Query().Count(proxy.Database.Ctx)
- assert.NoError(t, err)
-
- assert.Equal(t, before+1, after)
-
- all, err := proxy.Database.Client.RequestLog.Query().All(proxy.Database.Ctx)
- assert.NoError(t, err)
-
- requestLog := all[after-1]
- t.Log("--- HEADER ---")
- t.Log(requestLog.Headers)
-
- assert.Equal(t, "before", requestLog.Flag)
-}
diff --git a/core/errors.go b/core/errors.go
deleted file mode 100644
index 3a6241b..0000000
--- a/core/errors.go
+++ /dev/null
@@ -1,61 +0,0 @@
-package core
-
-import (
- "fmt"
- "net/http"
- "strings"
-
- "github.com/gin-gonic/gin"
-)
-
-var unauthorizedErrorTag = "failed to authorize users"
-var failedToSplitErrorTag = "failed to split error tag"
-
-var errorStatusMap = map[string]int{
- unauthorizedErrorTag: http.StatusUnauthorized,
-}
-
-func makeError(e error, tag string) error {
- if e == nil {
- return nil
- }
-
- return fmt.Errorf("%s: %v", tag, e)
-}
-
-func splitErrorTag(err error) (string, error) {
- errMsg := err.Error()
-
- splited := strings.Split(errMsg, ":")
- if len(splited) < 1 {
- return "", makeError(err, failedToSplitErrorTag)
- }
-
- return splited[0], nil
-}
-
-func findStatusCodeByErr(err *error) int {
- var statusCode = http.StatusInternalServerError
-
- tag, splitErr := splitErrorTag(*err)
- if splitErr != nil {
- return statusCode
- }
-
- if val, ok := errorStatusMap[tag]; ok {
- statusCode = val
- }
-
- return statusCode
-}
-
-func abordGin(proxy *TorimaProxy, err error, c *gin.Context) {
- statusCode := findStatusCodeByErr(&err)
- tag, _ := splitErrorTag(err)
- fmt.Printf("error: %d, %v, %v", statusCode, err, tag)
-
- c.Status(statusCode)
- c.Writer.WriteString(scripts)
- c.Writer.WriteString(backHTML)
- c.Abort()
-}
diff --git a/core/log.go b/core/log.go
deleted file mode 100644
index 604c3a1..0000000
--- a/core/log.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package core
-
-import (
- "fmt"
- "log"
- "net/http"
- "reflect"
- "runtime"
-)
-
-type FlowLog struct {
- name string
- result bool
-}
-
-type FlowLogger struct {
- logs []FlowLog
-}
-
-func NewFlowLogger() FlowLogger {
- return FlowLogger{
- logs: []FlowLog{},
- }
-}
-
-func (logger *FlowLogger) Add(f any, result bool) {
- rv := reflect.ValueOf(f)
- ptr := rv.Pointer()
- name := runtime.FuncForPC(ptr).Name()
-
- newLog := FlowLog{
- name,
- result,
- }
-
- logger.logs = append(logger.logs, newLog)
-}
-
-func (flowLogs *FlowLogger) Show() {
- log.Println("\n--- start ----")
-
- for _, v := range flowLogs.logs {
- log.Printf("name: %v\n", v.name)
- log.Printf("result: %v\n", v.result)
- }
-
- fmt.Println("--- end ----")
-}
-
-/**
- * LogReq is the function that logs the request.
-**/
-func LogReq(req *http.Request) {
- fmt.Printf("[%s] %s%s\n=> %s%s\n\n", req.Method, req.Host, req.RequestURI, req.URL.Host, req.URL.Path)
-}
diff --git a/core/modify_resp.go b/core/modify_resp.go
deleted file mode 100644
index 2aa0f80..0000000
--- a/core/modify_resp.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package core
-
-import (
- "bytes"
- "fmt"
- "io/ioutil"
- "log"
- "net/http"
- "strconv"
-
- "github.com/PuerkitoBio/goquery"
- "github.com/gin-gonic/gin"
-)
-
-func MainModifyResponse(proxy *TorimaProxy, res *http.Response) {
- fmt.Printf("=> %v\n", res.Request.URL)
-}
-
-func InjectHTMLModifyResponse(html string, proxy *TorimaProxy, res *http.Response, c *gin.Context) (bool, error) {
- document, err := goquery.NewDocumentFromReader(res.Body)
- if err != nil {
- log.Fatal(err)
- }
-
- document.Find("body").AppendHtml(html)
-
- html, err = document.Html()
- if err != nil {
- return FINISHED, err
- }
-
- // fmt.Printf("%v", html)
-
- b := []byte(html)
- res.Body = ioutil.NopCloser(bytes.NewReader(b))
-
- res.Header.Set("Content-Length", strconv.Itoa(len(b)))
- res.ContentLength = int64(len(b))
-
- return CONTINUE, nil
-}
-
-func InjectServiceWorkerModifyResponse(proxy *TorimaProxy, res *http.Response, c *gin.Context) (bool, error) {
- contentType := res.Header.Get("Content-Type")
-
- if contentType != "text/html; charset=utf-8" {
- return CONTINUE, nil
- }
-
- html := scripts + "\n"
-
- return InjectHTMLModifyResponse(html, proxy, res, c)
-}
diff --git a/core/param.go b/core/param.go
deleted file mode 100644
index 84ceae1..0000000
--- a/core/param.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package core
-
-import (
- gin_ninsho "github.com/ochanoco/ninsho/extension/gin"
-)
-
-/* configuration of DB */
-var DB_TYPE = readEnv("TORIMA_DB_TYPE", "sqlite3")
-var DB_CONFIG = readEnv("TORIMA_DB_CONFIG", "file:./data/db.sqlite3?_fk=1")
-var SECRET = readEnv("TORIMA_SECRET", randomString(32))
-
-/* other */
-var DEFAULT_DIRECTORS = []TorimaDirector{
- BeforeLogDirector,
- SanitizeHeaderDirector,
- AuthDirector,
- DefaultRouteDirector,
- ThirdPartyDirector,
- AfterLogDirector,
-}
-
-var DEFAULT_MODIFY_RESPONSES = []TorimaModifyResponse{
- InjectServiceWorkerModifyResponse,
-}
-
-var DEFAULT_PROXYWEB_PAGES = []TorimaProxyWebPage{
- ConfigWeb,
- StaticWeb,
- LoginWebs,
-}
-
-var CONFIG_FILE = "./config.yaml"
-var STATIC_FOLDER = "./static"
-
-var AUTH_PATH = gin_ninsho.NinshoGinPath{
- Unauthorized: "/auth/login",
- Callback: "/auth/callback",
- AfterAuth: "/_torima/back",
-}
-
-var CLIENT_ID = readEnvOrPanic("TORIMA_CLIENT_ID")
-var CLIENT_SECRET = readEnvOrPanic("TORIMA_CLIENT_SECRET")
diff --git a/core/params.go b/core/params.go
new file mode 100644
index 0000000..b9e07f9
--- /dev/null
+++ b/core/params.go
@@ -0,0 +1,14 @@
+package core
+
+import "github.com/ochanoco/torima/utils"
+
+/* configuration of DB */
+var DB_TYPE = utils.ReadEnv("TORIMA_DB_TYPE", "sqlite3")
+var DB_CONFIG = utils.ReadEnv("TORIMA_DB_CONFIG", "file:./data/db.sqlite3?_fk=1")
+var SECRET = utils.ReadEnv("TORIMA_SECRET", utils.RandomString(32))
+
+var CONFIG_FILE = "./config.yaml"
+var STATIC_FOLDER = "./static"
+
+var CLIENT_ID = utils.ReadEnvOrPanic("TORIMA_CLIENT_ID")
+var CLIENT_SECRET = utils.ReadEnvOrPanic("TORIMA_CLIENT_SECRET")
diff --git a/core/proxy.go b/core/proxy.go
index ccf5e02..2e3c6ae 100644
--- a/core/proxy.go
+++ b/core/proxy.go
@@ -4,50 +4,57 @@ import (
"net/http"
"github.com/gin-gonic/gin"
+ "github.com/ochanoco/torima/utils"
)
-const CONTINUE = true
-const FINISHED = false
-
-type TorimaPackageArgument interface{ *http.Request | *http.Response }
-
-func runAllPackage[T TorimaPackageArgument](
- pkgs []func(*TorimaProxy, T, *gin.Context) (bool, error),
- args T, proxy *TorimaProxy, c *gin.Context) {
-
- logger := NewFlowLogger()
+func runAllExtension[T TorimaPackageTarget](
+ pkgs []func(*TorimaPackageContext[T]) (int, error),
+ c *TorimaPackageContext[T]) {
for _, pkg := range pkgs {
- isContinuing, err := pkg(proxy, args, c)
- logger.Add(pkg, isContinuing)
+ status, err := pkg(c)
+
+ if status != Keep {
+ c.PackageStatus = status
+ }
if err != nil {
- abordGin(proxy, err, c)
+ utils.AbordGin(err, c.GinContext)
}
- if !isContinuing {
+ if status == ForceStop {
break
}
}
-
- logger.Show()
}
/**
* Directors is a list of functions that modify the
* request before it is sent to the target server.
**/
-func (proxy *TorimaProxy) Director(req *http.Request, c *gin.Context) {
- runAllPackage(proxy.Directors, req, proxy, c)
+func (proxy *TorimaProxy) Director(req *http.Request, ginContext *gin.Context) {
+ c := TorimaDirectorPackageContext{
+ Proxy: proxy,
+ Target: req,
+ GinContext: ginContext,
+ PackageStatus: AuthNeeded,
+ }
- LogReq(req)
+ runAllExtension[*http.Request](proxy.Directors, &c)
}
/**
* ModifyResponses is a list of functions that modify the
* response before it is sent to the client.
**/
-func (proxy *TorimaProxy) ModifyResponse(res *http.Response, c *gin.Context) error {
- runAllPackage(proxy.ModifyResponses, res, proxy, c)
+func (proxy *TorimaProxy) ModifyResponse(res *http.Response, ginContext *gin.Context) error {
+ c := TorimaModifyResponsePackageContext{
+ Proxy: proxy,
+ Target: res,
+ GinContext: ginContext,
+ PackageStatus: Keep,
+ }
+
+ runAllExtension(proxy.ModifyResponses, &c)
return nil
}
diff --git a/docker/Dockerfile b/docker/Dockerfile
index fbd5fe7..6c3727b 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -20,10 +20,15 @@ RUN nix-shell /docker/default.nix --command "go mod download"
# build serv
COPY ./core /workspace/core
-COPY ./serv /workspace/serv
+COPY ./proxy /workspace/proxy
+COPY ./extension /workspace/extension
+COPY ./utils /workspace/utils
COPY ./ent /workspace/ent
-COPY main.go /workspace
+COPY ./serv /workspace/serv
+
+WORKDIR /workspace/serv
RUN nix-shell /docker/default.nix --command "go build"
+WORKDIR /workspace
COPY ./static /workspace/static
-CMD ["/workspace/torima"]
\ No newline at end of file
+CMD ["/workspace/serv/serv"]
\ No newline at end of file
diff --git a/extension/directors/auth.go b/extension/directors/auth.go
new file mode 100644
index 0000000..65823b0
--- /dev/null
+++ b/extension/directors/auth.go
@@ -0,0 +1,49 @@
+package directors
+
+import (
+ "fmt"
+
+ "github.com/ochanoco/ninsho"
+ gin_ninsho "github.com/ochanoco/ninsho/extension/gin"
+ "github.com/ochanoco/torima/core"
+ "github.com/ochanoco/torima/utils"
+
+ "golang.org/x/exp/slices"
+)
+
+func SkipAuthDirector(c *core.TorimaDirectorPackageContext) (core.TorimaPackageStatus, error) {
+ if c.Target.Method == "GET" && c.Target.URL.RawQuery == "" {
+ if c.Target.URL.Path == "/" {
+ return core.NoAuthNeeded, nil
+ }
+
+ if slices.Contains(c.Proxy.Config.SkipAuthList, c.Target.URL.Path) {
+ return core.NoAuthNeeded, nil
+ }
+ }
+
+ return core.AuthNeeded, nil
+}
+
+func AuthDirector(c *core.TorimaDirectorPackageContext) (core.TorimaPackageStatus, error) {
+ if c.PackageStatus == core.NoAuthNeeded {
+ return core.NoAuthNeeded, nil
+ }
+
+ user, err := gin_ninsho.LoadUser[ninsho.LINE_USER](c.GinContext)
+
+ // just to be sure
+ c.Target.Header.Del("X-Torima-UserID")
+
+ if err != nil {
+ err = utils.MakeError(err, "failed to get user from session: ")
+ return core.ForceStop, err
+ }
+
+ if user != nil {
+ c.Target.Header.Set("X-Torima-UserID", user.Sub)
+ return core.Authed, nil
+ }
+
+ return core.ForceStop, utils.MakeError(fmt.Errorf(""), utils.UnauthorizedErrorTag)
+}
diff --git a/extension/directors/log.go b/extension/directors/log.go
new file mode 100644
index 0000000..c685821
--- /dev/null
+++ b/extension/directors/log.go
@@ -0,0 +1,40 @@
+package directors
+
+import (
+ "bytes"
+ "net/http/httputil"
+
+ "github.com/ochanoco/torima/core"
+ "github.com/ochanoco/torima/utils"
+)
+
+func MakeLogDirector(flag string) core.TorimaDirector {
+ return func(c *core.TorimaDirectorPackageContext) (core.TorimaPackageStatus, error) {
+ request, err := httputil.DumpRequest(c.Target, true)
+
+ if err != nil {
+ err = utils.MakeError(err, "failed to dump headers to json: ")
+ return core.ForceStop, err
+ }
+
+ splited := bytes.Split(request, []byte("\r\n\r\n"))
+
+ header := splited[0]
+ headerLen := len(header)
+
+ body := request[headerLen:]
+
+ l := c.Proxy.Database.CreateRequestLog(string(header), body, flag)
+ _, err = l.Save(c.Proxy.Database.Ctx)
+
+ if err != nil {
+ err = utils.MakeError(err, "failed to save request: ")
+ return core.ForceStop, err
+ }
+
+ return core.Keep, err
+ }
+}
+
+var BeforeLogDirector = MakeLogDirector("before")
+var AfterLogDirector = MakeLogDirector("after")
diff --git a/extension/directors/route.go b/extension/directors/route.go
new file mode 100644
index 0000000..cd6026f
--- /dev/null
+++ b/extension/directors/route.go
@@ -0,0 +1,35 @@
+package directors
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/ochanoco/torima/core"
+)
+
+func BasicRoute(host string, c *core.TorimaDirectorPackageContext) (core.TorimaPackageStatus, error) {
+ c.Target.URL.Host = host
+
+ // just to be sure
+ c.Target.Header.Del("X-Torima-Proxy-Token")
+ c.Target.Header.Set("X-Torima-Proxy-Token", core.SECRET)
+
+ c.Target.URL.Scheme = c.Proxy.Config.Scheme
+
+ return core.Keep, nil
+}
+
+func DefaultRouteDirector(c *core.TorimaDirectorPackageContext) (core.TorimaPackageStatus, error) {
+ if strings.HasPrefix(c.Target.URL.Path, "/torima/") {
+ return core.Keep, nil
+ }
+
+ host := c.Proxy.Config.DefaultOrigin
+
+ if host == "" {
+ err := fmt.Errorf("failed to get destination config (%s)", host)
+ return core.ForceStop, err
+ }
+
+ return BasicRoute(host, c)
+}
diff --git a/extension/directors/utils.go b/extension/directors/utils.go
new file mode 100644
index 0000000..db55442
--- /dev/null
+++ b/extension/directors/utils.go
@@ -0,0 +1,55 @@
+package directors
+
+import (
+ "net/http"
+ "strings"
+
+ "github.com/ochanoco/torima/core"
+)
+
+func ThirdPartyDirector(c *core.TorimaDirectorPackageContext) (core.TorimaPackageStatus, error) {
+ path := strings.Split(c.Target.URL.Path, "/")
+ hasRedirectPrefix := strings.HasPrefix(c.Target.URL.Path, "/torima/redirect/")
+
+ if !hasRedirectPrefix || len(path) < 3 {
+ return core.Keep, nil
+ }
+
+ for _, origin := range c.Proxy.Config.ProtectionScope {
+ if origin == path[3] {
+ c.Target.Host = origin
+ c.Target.URL.Host = origin
+
+ p := strings.Join(path[4:], "/")
+ c.Target.URL.Path = "/" + p
+
+ c.Target.URL.Scheme = "https"
+ return BasicRoute(origin, c)
+ }
+ }
+
+ return core.Keep, nil
+}
+
+func SanitizeHeaderDirector(c *core.TorimaDirectorPackageContext) (core.TorimaPackageStatus, error) {
+ headers := http.Header{
+ "Host": {c.Proxy.Config.Host},
+ "User-Agent": {"torima"},
+
+ "Content-Type": c.Target.Header["Content-Type"],
+ "Content-Length": c.Target.Header["Content-Length"],
+
+ "Accept": c.Target.Header["Accept"],
+ "Connection": c.Target.Header["Connection"],
+
+ "Accept-Encoding": c.Target.Header["Accept-Encoding"],
+ "Accept-Language": c.Target.Header["Accept-Language"],
+
+ "Cookie": c.Target.Header["Cookie"],
+ }
+
+ c.Target.Header = headers
+
+ return core.Keep, nil
+
+}
diff --git a/extension/modify_resp.go b/extension/modify_resp.go
new file mode 100644
index 0000000..d2feb7d
--- /dev/null
+++ b/extension/modify_resp.go
@@ -0,0 +1,54 @@
+package extension
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "strconv"
+
+ "github.com/PuerkitoBio/goquery"
+ "github.com/ochanoco/torima/core"
+ "github.com/ochanoco/torima/utils"
+)
+
+func MainModifyResponse(proxy *core.TorimaProxy, res *http.Response) {
+ fmt.Printf("=> %v\n", res.Request.URL)
+}
+
+func InjectHTML(html string, c *core.TorimaModifyResponsePackageContext) (core.TorimaPackageStatus, error) {
+ document, err := goquery.NewDocumentFromReader(c.Target.Body)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ document.Find("body").AppendHtml(html)
+
+ html, err = document.Html()
+ if err != nil {
+ return core.ForceStop, err
+ }
+
+ // fmt.Printf("%v", html)
+
+ b := []byte(html)
+ c.Target.Body = io.NopCloser(bytes.NewReader(b))
+
+ c.Target.Header.Set("Content-Length", strconv.Itoa(len(b)))
+ c.Target.ContentLength = int64(len(b))
+
+ return core.Keep, nil
+}
+
+func InjectServiceWorkerModifyResponse(c *core.TorimaModifyResponsePackageContext) (core.TorimaPackageStatus, error) {
+ contentType := c.Target.Header.Get("Content-Type")
+
+ if contentType != "text/html; charset=utf-8" {
+ return core.Keep, nil
+ }
+
+ html := utils.Scripts + "\n"
+
+ return InjectHTML(html, c)
+}
diff --git a/extension/param.go b/extension/param.go
new file mode 100644
index 0000000..9c0ac7c
--- /dev/null
+++ b/extension/param.go
@@ -0,0 +1,11 @@
+package extension
+
+import (
+ gin_ninsho "github.com/ochanoco/ninsho/extension/gin"
+)
+
+var AUTH_PATH = gin_ninsho.NinshoGinPath{
+ Unauthorized: "/auth/login",
+ Callback: "/auth/callback",
+ AfterAuth: "/_torima/back",
+}
diff --git a/core/web.go b/extension/web.go
similarity index 78%
rename from core/web.go
rename to extension/web.go
index 98ce020..ad699b5 100644
--- a/core/web.go
+++ b/extension/web.go
@@ -1,4 +1,4 @@
-package core
+package extension
import (
"fmt"
@@ -7,39 +7,40 @@ import (
"github.com/gin-gonic/gin"
"github.com/ochanoco/ninsho"
gin_ninsho "github.com/ochanoco/ninsho/extension/gin"
+ "github.com/ochanoco/torima/core"
)
-func StaticWeb(proxy *TorimaProxy, r *gin.RouterGroup) {
+func StaticWeb(proxy *core.TorimaProxy, r *gin.RouterGroup) {
r.Use(func() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Service-Worker-Allowed", "/")
}
}())
- r.Static("/static", STATIC_FOLDER)
+ r.Static("/static", core.STATIC_FOLDER)
}
-func ConfigWeb(proxy *TorimaProxy, r *gin.RouterGroup) {
+func ConfigWeb(proxy *core.TorimaProxy, r *gin.RouterGroup) {
r.GET("/status", func(c *gin.Context) {
session := sessions.Default(c)
userId := session.Get("userId")
c.JSON(200, gin.H{
"protection_scope": proxy.Config.ProtectionScope,
- "white_list_path": proxy.Config.WhiteListPath,
+ "skip_auth_list": proxy.Config.SkipAuthList,
"is_authenticated": userId != nil, // is it needed?.
})
})
}
-func LoginWebs(proxy *TorimaProxy, r *gin.RouterGroup) {
+func LoginWebs(proxy *core.TorimaProxy, r *gin.RouterGroup) {
var redirectUri = proxy.Config.Host + proxy.Config.WebRoot + AUTH_PATH.Callback
fmt.Printf("please set '%v' to redirect uri\n", redirectUri)
var provider = ninsho.Provider{
- ClientID: CLIENT_ID,
- ClientSecret: CLIENT_SECRET,
+ ClientID: core.CLIENT_ID,
+ ClientSecret: core.CLIENT_SECRET,
RedirectUri: redirectUri,
Scope: "profile openid",
UsePKCE: true,
diff --git a/main.go b/main.go
index 2557b44..bae2029 100644
--- a/main.go
+++ b/main.go
@@ -1,9 +1 @@
-package main
-
-import (
- "github.com/ochanoco/torima/serv"
-)
-
-func main() {
- serv.Main()
-}
+package torima
diff --git a/core/serv.go b/proxy/main.go
similarity index 59%
rename from core/serv.go
rename to proxy/main.go
index ec17e1f..f6afb1c 100644
--- a/core/serv.go
+++ b/proxy/main.go
@@ -1,4 +1,4 @@
-package core
+package proxy
import (
"fmt"
@@ -7,21 +7,23 @@ import (
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
+ "github.com/ochanoco/torima/core"
+ "github.com/ochanoco/torima/utils"
)
-func ProxyServer() (*TorimaProxy, error) {
- secret := randomString(64)
+func ProxyServer() (*core.TorimaProxy, error) {
+ secret := utils.RandomString(64)
r := gin.Default()
store := cookie.NewStore([]byte(secret))
r.Use(sessions.Sessions("torima-session", store))
- db, err := InitDB(DB_CONFIG)
+ db, err := core.InitDB(core.DB_CONFIG)
if err != nil {
log.Fatalf("failed to init db: %v", err)
}
- config, err := readConfig()
+ config, err := core.ReadConfig()
if config == nil {
panic("failed to read config: " + err.Error())
}
@@ -32,7 +34,7 @@ func ProxyServer() (*TorimaProxy, error) {
printConfig(config)
- proxy := NewOchancoProxy(r, DEFAULT_DIRECTORS, DEFAULT_MODIFY_RESPONSES, DEFAULT_PROXYWEB_PAGES, config, db)
+ proxy := core.NewOchancoProxy(r, DEFAULT_DIRECTORS, DEFAULT_MODIFY_RESPONSES, DEFAULT_PROXYWEB_PAGES, config, db)
return &proxy, nil
}
diff --git a/proxy/param.go b/proxy/param.go
new file mode 100644
index 0000000..253f206
--- /dev/null
+++ b/proxy/param.go
@@ -0,0 +1,28 @@
+package proxy
+
+import (
+ "github.com/ochanoco/torima/core"
+ "github.com/ochanoco/torima/extension"
+ "github.com/ochanoco/torima/extension/directors"
+)
+
+/* other */
+var DEFAULT_DIRECTORS = core.TorimaDirectors{
+ directors.BeforeLogDirector,
+ directors.SanitizeHeaderDirector,
+ directors.SkipAuthDirector,
+ directors.AuthDirector,
+ directors.DefaultRouteDirector,
+ directors.ThirdPartyDirector,
+ directors.AfterLogDirector,
+}
+
+var DEFAULT_MODIFY_RESPONSES = core.TorimaModifyResponses{
+ extension.InjectServiceWorkerModifyResponse,
+}
+
+var DEFAULT_PROXYWEB_PAGES = []core.TorimaProxyWebPage{
+ extension.ConfigWeb,
+ extension.StaticWeb,
+ extension.LoginWebs,
+}
diff --git a/proxy/utils.go b/proxy/utils.go
new file mode 100644
index 0000000..2bbe1a1
--- /dev/null
+++ b/proxy/utils.go
@@ -0,0 +1,18 @@
+package proxy
+
+import (
+ "fmt"
+
+ "github.com/ochanoco/torima/core"
+)
+
+func printConfig(config *core.TorimaConfig) {
+ fmt.Println("default_origin:", config.DefaultOrigin)
+ fmt.Println("host:", config.Host)
+ fmt.Println("port:", config.Port)
+ fmt.Println("scheme:", config.Scheme)
+
+ fmt.Println("skip_auth_list:", config.SkipAuthList)
+ fmt.Println("protection_scope:", config.ProtectionScope)
+ fmt.Println("web_root:", config.WebRoot)
+}
diff --git a/serv/main.go b/serv/main.go
index ee97754..431db9b 100644
--- a/serv/main.go
+++ b/serv/main.go
@@ -1,19 +1,20 @@
-package serv
+package main
import (
"fmt"
"github.com/ochanoco/torima/core"
+ "github.com/ochanoco/torima/proxy"
)
const NAME = "line"
func Run() (*core.TorimaProxy, error) {
- proxyServ, err := core.ProxyServer()
+ proxyServ, err := proxy.ProxyServer()
return proxyServ, err
}
-func Main() {
+func main() {
proxyServ, err := Run()
if err != nil {
panic(err)
diff --git a/core/config_test.go b/test/config_test.go
similarity index 71%
rename from core/config_test.go
rename to test/config_test.go
index 9400b6a..af02143 100644
--- a/core/config_test.go
+++ b/test/config_test.go
@@ -1,16 +1,18 @@
-package core
+package test
import (
"os"
"testing"
+ "github.com/ochanoco/torima/core"
+ "github.com/ochanoco/torima/utils"
"github.com/stretchr/testify/assert"
)
var TEST_CONFIG = `
port: 9000
-white_list_path:
+skip_auth_list:
- /favicon.ico
default_origin: 127.0.0.1:9000
@@ -21,16 +23,16 @@ protection_scope:
scheme: http
`
-func readTestConfig(t *testing.T) (*TorimaConfig, *os.File, error) {
+func readTestConfig(t *testing.T) (*core.TorimaConfig, *os.File, error) {
file, err := os.CreateTemp("", "config.yaml")
assert.NoError(t, err)
- CONFIG_FILE = file.Name()
+ core.CONFIG_FILE = file.Name()
_, err = file.Write([]byte(TEST_CONFIG))
assert.NoError(t, err)
- config, err := readConfig()
+ config, err := core.ReadConfig()
return config, file, err
}
@@ -45,14 +47,14 @@ func TestReadConfig(t *testing.T) {
assert.Equal(t, 9000, config.Port)
assert.Equal(t, "127.0.0.1:9000", config.DefaultOrigin)
assert.Equal(t, "http", config.Scheme)
- assert.Equal(t, "/favicon.ico", config.WhiteListPath[0])
+ assert.Equal(t, "/favicon.ico", config.SkipAuthList[0])
assert.Equal(t, "example.com", config.ProtectionScope[0])
}
func TestReadConfigDefault(t *testing.T) {
- CONFIG_FILE = ""
+ core.CONFIG_FILE = ""
- config, err := readConfig()
+ config, err := core.ReadConfig()
assert.Error(t, err)
assert.NotNil(t, config)
@@ -60,7 +62,7 @@ func TestReadConfigDefault(t *testing.T) {
assert.Equal(t, "http://127.0.0.1:8080", config.Host)
assert.Equal(t, 8080, config.Port)
assert.Equal(t, "http", config.Scheme)
- assert.Equal(t, 0, len(config.WhiteListPath))
+ assert.Equal(t, 0, len(config.SkipAuthList))
assert.Equal(t, 0, len(config.ProtectionScope))
assert.Equal(t, "/torima", config.WebRoot)
}
@@ -69,9 +71,9 @@ func TestReadConfigDefault(t *testing.T) {
func TestReadEnv(t *testing.T) {
os.Setenv("TORIMA_TEST1", "TEST")
- env := readEnv("TORIMA_TEST1", "TEST")
+ env := utils.ReadEnv("TORIMA_TEST1", "TEST")
assert.Equal(t, "TEST", env)
- env = readEnv("TORIMA_TEST2", "TEST")
+ env = utils.ReadEnv("TORIMA_TEST2", "TEST")
assert.Equal(t, "TEST", env)
}
diff --git a/test/director_test.go b/test/director_test.go
new file mode 100644
index 0000000..19b11ec
--- /dev/null
+++ b/test/director_test.go
@@ -0,0 +1,277 @@
+package test
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "os"
+ "testing"
+
+ "github.com/gin-contrib/sessions"
+ "github.com/gin-contrib/sessions/cookie"
+ "github.com/gin-gonic/gin"
+ "github.com/ochanoco/ninsho"
+ "github.com/ochanoco/torima/core"
+ "github.com/ochanoco/torima/extension/directors"
+ "github.com/ochanoco/torima/proxy"
+ "github.com/ochanoco/torima/test/tools"
+ "github.com/stretchr/testify/assert"
+)
+
+func directorSample(t *testing.T) (*core.TorimaPackageContext[*http.Request], *TestResponseRecorder) {
+ logger := tools.ExtensionLogger{}
+
+ core.DB_TYPE = "sqlite3"
+ core.DB_CONFIG = "../data/test.db?_fk=1"
+ core.SECRET = "test_secret"
+
+ recorder := CreateTestResponseRecorder()
+ ginContext, r := gin.CreateTestContext(recorder)
+
+ store := cookie.NewStore([]byte("test"))
+ r.Use(sessions.Sessions("torima-session", store))
+
+ db, err := core.InitDB(core.DB_CONFIG)
+ assert.NoError(t, err)
+
+ config, file, err := readTestConfig(t)
+ assert.NoError(t, err)
+ defer os.Remove(file.Name())
+
+ proxy := core.NewOchancoProxy(r, logger.InjectDirectors(proxy.DEFAULT_DIRECTORS), proxy.DEFAULT_MODIFY_RESPONSES, proxy.DEFAULT_PROXYWEB_PAGES, config, db)
+ req := httptest.NewRequest("GET", "http://localhost:8080/", nil)
+
+ ctx := core.TorimaPackageContext[*http.Request]{
+ GinContext: ginContext,
+ Proxy: &proxy,
+ Target: req,
+ }
+
+ return &ctx, recorder
+}
+
+func setupMockServer(handler http.HandlerFunc, req *http.Request, t *testing.T) (*httptest.Server, *url.URL) {
+ h := http.HandlerFunc(handler)
+
+ ts := httptest.NewServer(h)
+ u, err := url.Parse(ts.URL)
+ assert.NoError(t, err)
+
+ req.URL.Path = "/hello"
+ req.URL.Host = u.Host
+ req.Host = u.Host
+
+ return ts, u
+}
+
+// test for RouteDirector
+func TestRouteDirector(t *testing.T) {
+ ctx, _ := directorSample(t)
+ c, err := directors.BasicRoute("example.com", ctx)
+
+ assert.NoError(t, err)
+ assert.Equal(t, core.Keep, c)
+ assert.Equal(t, "example.com", ctx.Target.URL.Host)
+ assert.Equal(t, "http", ctx.Target.URL.Scheme)
+ assert.Equal(t, core.SECRET, ctx.Target.Header.Get("X-Torima-Proxy-Token"))
+}
+
+// test for DefaultRouteDirector
+func TestThirdPartyDirector(t *testing.T) {
+ h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintln(w, "Hello, client")
+ })
+
+ ts := httptest.NewServer(h)
+ defer ts.Close()
+
+ u, err := url.Parse(ts.URL)
+ assert.NoError(t, err)
+
+ host := fmt.Sprintf("%v:%v", u.Host, u.Port())
+
+ ctx, _ := directorSample(t)
+
+ ctx.Target.URL.Path = "/torima/redirect/" + host
+
+ ctx.Proxy.Config.ProtectionScope = []string{host}
+
+ c, err := directors.ThirdPartyDirector(ctx)
+ assert.NoError(t, err)
+ assert.Equal(t, core.Keep, c)
+
+ c, err = directors.ThirdPartyDirector(ctx)
+ assert.NoError(t, err)
+
+ assert.Equal(t, core.Keep, c)
+ assert.Equal(t, host, ctx.Target.URL.Host)
+}
+
+// test for DefaultRouteDirector
+func TestThirdPartyDirectorNoParmit(t *testing.T) {
+ unpermitHost := "not-in-list.example.com"
+
+ ctx, _ := directorSample(t)
+
+ ctx.Target.URL.Path = "/torima/redirect/" + unpermitHost + "/"
+
+ c, err := directors.ThirdPartyDirector(ctx)
+ assert.NoError(t, err)
+ assert.Equal(t, core.Keep, c)
+
+ c, err = directors.ThirdPartyDirector(ctx)
+ assert.NoError(t, err)
+
+ assert.Equal(t, core.Keep, c)
+ assert.NotEqual(t, unpermitHost, ctx.Target.URL.Host)
+}
+
+// test for AuthDirector
+func TestAuthDirector(t *testing.T) {
+ h := func(w http.ResponseWriter, r *http.Request) {
+ assert.Equal(t, "1", r.Header.Get("X-Torima-UserID"))
+ fmt.Fprintln(w, "Hello, client")
+ }
+
+ testDirector := func(c *core.TorimaPackageContext[*http.Request]) (core.TorimaPackageStatus, error) {
+ session := sessions.Default(c.GinContext)
+
+ user := ninsho.LINE_USER{
+ Sub: "1",
+ }
+ json, _ := json.Marshal(user)
+
+ session.Set("user", string(json))
+ err := session.Save()
+ assert.NoError(t, err)
+
+ status, err := directors.AuthDirector(c)
+
+ assert.NoError(t, err)
+ assert.Equal(t, core.Authed, status)
+
+ return core.Authed, nil
+ }
+
+ proxy.DEFAULT_DIRECTORS = core.TorimaDirectors{
+ testDirector,
+ }
+
+ ctx, recorder := directorSample(t)
+ mockServer, _ := setupMockServer(h, ctx.Target, t)
+ defer mockServer.Close()
+
+ ctx.Target.URL.Path = "/hello?hoge"
+
+ ctx.Proxy.Engine.ServeHTTP(recorder, ctx.Target)
+ assert.Equal(t, http.StatusOK, recorder.Result().StatusCode)
+}
+
+func TestSkipAuthList(t *testing.T) {
+ h := func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintln(w, "Hello, client")
+ }
+
+ proxy.DEFAULT_DIRECTORS = core.TorimaDirectors{
+ directors.SkipAuthDirector,
+ directors.AuthDirector,
+ }
+
+ ctx, recorder := directorSample(t)
+ mockServer, _ := setupMockServer(h, ctx.Target, t)
+ defer mockServer.Close()
+
+ ctx.Proxy.Config.SkipAuthList = []string{
+ "/hello",
+ }
+
+ ctx.Target.URL.Path = "/hello"
+ ctx.Proxy.Engine.ServeHTTP(recorder, ctx.Target)
+ assert.Equal(t, http.StatusOK, recorder.Result().StatusCode)
+}
+
+func TestForceAuthList(t *testing.T) {
+ h := func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintln(w, "Hello, client")
+ }
+
+ proxy.DEFAULT_DIRECTORS = core.TorimaDirectors{
+ directors.DefaultRouteDirector,
+ directors.ForceAuthDirector,
+ directors.AuthDirector,
+ }
+
+ ctx, recorder := directorSample(t)
+ mockServer, _ := setupMockServer(h, ctx.Target, t)
+ defer mockServer.Close()
+
+ ctx.Proxy.Config.ForceAuthList = []string{
+ "/",
+ }
+
+ ctx.Target.URL.Path = "/"
+ ctx.Proxy.Engine.ServeHTTP(recorder, ctx.Target)
+ assert.Equal(t, http.StatusUnauthorized, recorder.Result().StatusCode)
+}
+
+// test for AuthDirector
+func TestAuthDirectorNoPermit(t *testing.T) {
+ proxy.DEFAULT_DIRECTORS = core.TorimaDirectors{
+ directors.AuthDirector,
+ }
+
+ ctx, recorder := directorSample(t)
+ ctx.Target.URL.Path = "/hello"
+
+ ctx.Proxy.Engine.ServeHTTP(recorder, ctx.Target)
+ assert.Equal(t, http.StatusUnauthorized, recorder.Result().StatusCode)
+}
+
+type TestResponseRecorder struct {
+ *httptest.ResponseRecorder
+ closeChannel chan bool
+}
+
+func (r *TestResponseRecorder) CloseNotify() <-chan bool {
+ return r.closeChannel
+}
+
+func (r *TestResponseRecorder) closeClient() {
+ r.closeChannel <- true
+}
+
+func CreateTestResponseRecorder() *TestResponseRecorder {
+ return &TestResponseRecorder{
+ httptest.NewRecorder(),
+ make(chan bool, 1),
+ }
+}
+
+func TestLogDirector(t *testing.T) {
+ ctx, recorder := directorSample(t)
+
+ before, err := ctx.Proxy.Database.Client.RequestLog.Query().Count(ctx.Proxy.Database.Ctx)
+ assert.NoError(t, err)
+
+ ctx.Target.URL.Path = "/"
+
+ directors.BeforeLogDirector(ctx)
+
+ assert.Equal(t, http.StatusOK, recorder.Result().StatusCode)
+
+ after, err := ctx.Proxy.Database.Client.RequestLog.Query().Count(ctx.Proxy.Database.Ctx)
+ assert.NoError(t, err)
+
+ assert.Equal(t, before+1, after)
+
+ all, err := ctx.Proxy.Database.Client.RequestLog.Query().All(ctx.Proxy.Database.Ctx)
+ assert.NoError(t, err)
+
+ requestLog := all[after-1]
+ t.Log("--- HEADER ---")
+ t.Log(requestLog.Headers)
+
+ assert.Equal(t, "before", requestLog.Flag)
+}
diff --git a/core/errors_test.go b/test/errors_test.go
similarity index 61%
rename from core/errors_test.go
rename to test/errors_test.go
index 31ddd84..f63fff4 100644
--- a/core/errors_test.go
+++ b/test/errors_test.go
@@ -1,16 +1,17 @@
-package core
+package test
import (
"errors"
"testing"
+ "github.com/ochanoco/torima/utils"
"github.com/stretchr/testify/assert"
)
// test for splitErrorTagfunc TestSplitErrorTag() {
func TestSplitErrorTag(t *testing.T) {
err := errors.New("test error: this is test error")
- tag, err := splitErrorTag(err)
+ tag, err := utils.SplitErrorTag(err)
assert.NoError(t, err)
assert.Equal(t, "test error", tag)
@@ -19,11 +20,11 @@ func TestSplitErrorTag(t *testing.T) {
// test for findStatusCodeByErr
func TestFindStatusCodeByErr(t *testing.T) {
err := errors.New("")
- unauthorizedErr := makeError(err, unauthorizedErrorTag)
- unexpectedErr := makeError(err, "unexpected error")
+ unauthorizedErr := utils.MakeError(err, utils.UnauthorizedErrorTag)
+ unexpectedErr := utils.MakeError(err, "unexpected error")
- unauthorizedErrStatusCode := findStatusCodeByErr(&unauthorizedErr)
- unexpectedError := findStatusCodeByErr(&unexpectedErr)
+ unauthorizedErrStatusCode := utils.FindStatusCodeByErr(&unauthorizedErr)
+ unexpectedError := utils.FindStatusCodeByErr(&unexpectedErr)
assert.Equal(t, 401, unauthorizedErrStatusCode)
assert.Equal(t, 500, unexpectedError)
diff --git a/test/log_test.go b/test/log_test.go
new file mode 100644
index 0000000..0b56204
--- /dev/null
+++ b/test/log_test.go
@@ -0,0 +1,36 @@
+package test
+
+import (
+ "fmt"
+ "testing"
+
+ "github.com/ochanoco/torima/core"
+ "github.com/ochanoco/torima/extension/directors"
+ "github.com/ochanoco/torima/test/tools"
+)
+
+func TestExtensionLog(t *testing.T) {
+ core.DB_TYPE = "sqlite3"
+ core.DB_CONFIG = "../data/test.db?_fk=1"
+ core.SECRET = "test_secret"
+
+ logger := tools.ExtensionLogger{}
+
+ directors := core.TorimaDirectors{
+ directors.DefaultRouteDirector,
+ directors.DefaultRouteDirector,
+ }
+
+ directors = logger.InjectDirectors(directors)
+
+ if len(directors) != 5 {
+ t.Errorf("InjectDirector failed")
+ }
+
+ fmt.Printf("Directors: %v\n", directors)
+
+ c, _ := directorSample(t)
+ c.Proxy.Directors = directors
+
+ c.Proxy.Director(c.Target, c.GinContext)
+}
diff --git a/test/tools/log.go b/test/tools/log.go
new file mode 100644
index 0000000..7179063
--- /dev/null
+++ b/test/tools/log.go
@@ -0,0 +1,76 @@
+package tools
+
+import (
+ "log"
+ "reflect"
+ "runtime"
+
+ "github.com/ochanoco/torima/core"
+)
+
+var STATE = map[int]string{
+ 0: "AuthNeeded",
+ 1: "Authed",
+ 2: "NoAuthNeeded",
+ 3: "ForceStop",
+ 4: "Keep",
+}
+
+type ExtensionLogger struct {
+}
+
+func (logger *ExtensionLogger) Director(count int) core.TorimaDirector {
+ return func(c *core.TorimaDirectorPackageContext) (int, error) {
+ Log(count, c.Proxy.Directors[count*2+2], c.PackageStatus, c.Target.URL.Path)
+ return core.Keep, nil
+ }
+}
+
+func (logger *ExtensionLogger) ModifyResp(count int) core.TorimaModifyResponse {
+ return func(c *core.TorimaModifyResponsePackageContext) (int, error) {
+ Log(count, c.Proxy.Directors[count*2+2], c.PackageStatus, "")
+ return core.Keep, nil
+ }
+}
+
+func StartOrEndDirector[T *core.TorimaDirectorPackageContext | *core.TorimaModifyResponsePackageContext](c T) (int, error) {
+ println("---------------------")
+ return core.Keep, nil
+}
+
+func (logger *ExtensionLogger) InjectDirectors(source core.TorimaDirectors) core.TorimaDirectors {
+ result := core.TorimaDirectors{StartOrEndDirector[*core.TorimaDirectorPackageContext]}
+
+ for i, v := range source {
+ d := logger.Director(i)
+ result = append(result, d)
+ result = append(result, v)
+ }
+
+ return result
+}
+
+func (logger *ExtensionLogger) InjectModifyResps(source core.TorimaModifyResponses) core.TorimaModifyResponses {
+ result := core.TorimaModifyResponses{StartOrEndDirector[*core.TorimaModifyResponsePackageContext]}
+
+ for i, v := range source {
+ d := logger.ModifyResp(i)
+ result = append(result, d)
+ result = append(result, v)
+ }
+
+ return result
+}
+
+func Log(count int, extension any, result int, path string) {
+ rv1 := reflect.ValueOf(extension)
+ ptr1 := rv1.Pointer()
+
+ extensionName := runtime.FuncForPC(ptr1).Name()
+
+ log.Printf("id: %v\n", count)
+ log.Printf("name: %v\n", extensionName)
+ log.Printf("result: %v\n", STATE[result])
+ log.Printf("path: %v\n", path)
+
+}
diff --git a/utils/env.go b/utils/env.go
new file mode 100644
index 0000000..2f63854
--- /dev/null
+++ b/utils/env.go
@@ -0,0 +1,28 @@
+package utils
+
+import (
+ "fmt"
+ "log"
+ "os"
+)
+
+func ReadEnv(name, def string) string {
+ value := os.Getenv(name)
+
+ if value == "" {
+ fmt.Printf("environment variable '%v' is not found so that proxy use '%v'\n", name, def)
+ value = def
+ }
+
+ return value
+}
+
+func ReadEnvOrPanic(name string) string {
+ value := os.Getenv(name)
+
+ if value == "" {
+ log.Fatalf("environment variable '%v' is not found", name)
+ }
+
+ return value
+}
diff --git a/utils/errors.go b/utils/errors.go
new file mode 100644
index 0000000..b291aa8
--- /dev/null
+++ b/utils/errors.go
@@ -0,0 +1,61 @@
+package utils
+
+import (
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/gin-gonic/gin"
+)
+
+var UnauthorizedErrorTag = "failed to authorize users"
+var FailedToSplitErrorTag = "failed to split error tag"
+
+var errorStatusMap = map[string]int{
+ UnauthorizedErrorTag: http.StatusUnauthorized,
+}
+
+func MakeError(e error, tag string) error {
+ if e == nil {
+ return nil
+ }
+
+ return fmt.Errorf("%s: %v", tag, e)
+}
+
+func SplitErrorTag(err error) (string, error) {
+ errMsg := err.Error()
+
+ splited := strings.Split(errMsg, ":")
+ if len(splited) < 1 {
+ return "", MakeError(err, FailedToSplitErrorTag)
+ }
+
+ return splited[0], nil
+}
+
+func FindStatusCodeByErr(err *error) int {
+ var statusCode = http.StatusInternalServerError
+
+ tag, splitErr := SplitErrorTag(*err)
+ if splitErr != nil {
+ return statusCode
+ }
+
+ if val, ok := errorStatusMap[tag]; ok {
+ statusCode = val
+ }
+
+ return statusCode
+}
+
+func AbordGin(err error, c *gin.Context) {
+ statusCode := FindStatusCodeByErr(&err)
+ tag, _ := SplitErrorTag(err)
+ fmt.Printf("error: %d, %v, %v", statusCode, err, tag)
+
+ c.Status(statusCode)
+ c.Writer.WriteString(Scripts)
+ c.Writer.WriteString(BackHTML)
+ c.Abort()
+}
diff --git a/core/html.go b/utils/html.go
similarity index 87%
rename from core/html.go
rename to utils/html.go
index 2fc5ef3..9fe2e55 100644
--- a/core/html.go
+++ b/utils/html.go
@@ -1,12 +1,12 @@
-package core
+package utils
-var scripts = `
+var Scripts = `
`
-var backHTML = `
+var BackHTML = `
`
diff --git a/core/utils.go b/utils/utils.go
similarity index 81%
rename from core/utils.go
rename to utils/utils.go
index a20fe79..6308903 100644
--- a/core/utils.go
+++ b/utils/utils.go
@@ -1,8 +1,8 @@
-package core
+package utils
import "math/rand"
-func randomString(n int) string {
+func RandomString(n int) string {
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
s := make([]rune, n)