mirror of
https://github.com/1f349/tulip.git
synced 2024-12-23 00:34:07 +00:00
Start working on oauth controller
This commit is contained in:
parent
54f67abffc
commit
0926bf9327
@ -11,8 +11,12 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"github.com/1f349/mjwt"
|
"github.com/1f349/mjwt"
|
||||||
"github.com/1f349/tulip/purple-server"
|
clientStore "github.com/1f349/tulip/client-store"
|
||||||
"github.com/1f349/tulip/purple-server/pages"
|
"github.com/1f349/tulip/cmd/purple-tulip/pages"
|
||||||
|
"github.com/1f349/tulip/cmd/purple-tulip/server"
|
||||||
|
"github.com/1f349/tulip/database"
|
||||||
|
"github.com/1f349/tulip/oauth"
|
||||||
|
"github.com/1f349/tulip/openid"
|
||||||
"github.com/1f349/violet/utils"
|
"github.com/1f349/violet/utils"
|
||||||
exitReload "github.com/MrMelon54/exit-reload"
|
exitReload "github.com/MrMelon54/exit-reload"
|
||||||
"github.com/google/subcommands"
|
"github.com/google/subcommands"
|
||||||
@ -38,10 +42,10 @@ func (s *serveCmd) Usage() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *serveCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
func (s *serveCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||||
log.Println("[Lavender] Starting...")
|
log.Println("[PurpleTulip] Starting...")
|
||||||
|
|
||||||
if s.configPath == "" {
|
if s.configPath == "" {
|
||||||
log.Println("[Lavender] Error: config flag is missing")
|
log.Println("[PurpleTulip] Error: config flag is missing")
|
||||||
return subcommands.ExitUsageError
|
return subcommands.ExitUsageError
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,45 +53,53 @@ func (s *serveCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...interface{})
|
|||||||
err := loadConfig(s.configPath, &conf)
|
err := loadConfig(s.configPath, &conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
log.Println("[Lavender] Error: missing config file")
|
log.Println("[PurpleTulip] Error: missing config file")
|
||||||
} else {
|
} else {
|
||||||
log.Println("[Lavender] Error: loading config file: ", err)
|
log.Println("[PurpleTulip] Error: loading config file: ", err)
|
||||||
}
|
}
|
||||||
return subcommands.ExitFailure
|
return subcommands.ExitFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
configPathAbs, err := filepath.Abs(s.configPath)
|
configPathAbs, err := filepath.Abs(s.configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("[Lavender] Failed to get absolute config path")
|
log.Fatal("[PurpleTulip] Failed to get absolute config path")
|
||||||
}
|
}
|
||||||
wd := filepath.Dir(configPathAbs)
|
wd := filepath.Dir(configPathAbs)
|
||||||
|
|
||||||
mSign, err := mjwt.NewMJwtSignerFromFileOrCreate(conf.Issuer, filepath.Join(wd, "lavender.private.key"), rand.Reader, 4096)
|
signer, err := mjwt.NewMJwtSignerFromFileOrCreate(conf.Issuer, filepath.Join(wd, "purple-tulip.private.key.pem"), rand.Reader, 4096)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("[Lavender] Failed to load or create MJWT signer:", err)
|
log.Fatal("[PurpleTulip] Failed to load or create MJWT signer:", err)
|
||||||
|
}
|
||||||
|
saveMjwtPubKey(signer, wd)
|
||||||
|
|
||||||
|
db, err := database.Open(filepath.Join(wd, "purple-tulip.db.sqlite"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("[PurpleTulip] Failed to open database:", err)
|
||||||
}
|
}
|
||||||
saveMjwtPubKey(mSign, wd)
|
|
||||||
|
|
||||||
if err := pages.LoadPages(wd); err != nil {
|
if err := pages.LoadPages(wd); err != nil {
|
||||||
log.Fatal("[Lavender] Failed to load page templates:", err)
|
log.Fatal("[PurpleTulip] Failed to load page templates:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
srv := server.NewHttpServer(conf, mSign)
|
openIdConf := openid.GenConfig(conf.BaseUrl, []string{"openid", "name", "username", "profile", "email", "birthdate", "age", "zoneinfo", "locale"}, []string{"sub", "name", "preferred_username", "profile", "picture", "website", "email", "email_verified", "gender", "birthdate", "zoneinfo", "locale", "updated_at"})
|
||||||
log.Printf("[Lavender] Starting HTTP red-server on '%s'\n", srv.Server.Addr)
|
controller := oauth.NewOAuthController(signer, &server.PurpleAuthSource{DB: db}, clientStore.New(db), openIdConf)
|
||||||
|
|
||||||
|
srv := server.server.NewHttpServer(conf, db, controller, signer)
|
||||||
|
log.Printf("[PurpleTulip] Starting HTTP server on '%s'\n", srv.Server.Addr)
|
||||||
go utils.RunBackgroundHttp("HTTP", srv.Server)
|
go utils.RunBackgroundHttp("HTTP", srv.Server)
|
||||||
|
|
||||||
exitReload.ExitReload("Lavender", func() {
|
exitReload.ExitReload("PurpleTulip", func() {
|
||||||
var conf server.Conf
|
var conf server.Conf
|
||||||
err := loadConfig(s.configPath, &conf)
|
err := loadConfig(s.configPath, &conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("[Lavender] Failed to read config:", err)
|
log.Println("[PurpleTulip] Failed to read config:", err)
|
||||||
}
|
}
|
||||||
err = srv.UpdateConfig(conf)
|
err = srv.UpdateConfig(conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("[Lavender] Failed to reload config:", err)
|
log.Println("[PurpleTulip] Failed to reload config:", err)
|
||||||
}
|
}
|
||||||
}, func() {
|
}, func() {
|
||||||
// stop http red-server
|
// stop http server
|
||||||
_ = srv.Server.Close()
|
_ = srv.Server.Close()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -108,10 +120,10 @@ func saveMjwtPubKey(mSign mjwt.Signer, wd string) {
|
|||||||
b := new(bytes.Buffer)
|
b := new(bytes.Buffer)
|
||||||
err := pem.Encode(b, &pem.Block{Type: "RSA PUBLIC KEY", Bytes: pubKey})
|
err := pem.Encode(b, &pem.Block{Type: "RSA PUBLIC KEY", Bytes: pubKey})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("[Lavender] Failed to encode MJWT public key:", err)
|
log.Fatal("[PurpleTulip] Failed to encode MJWT public key:", err)
|
||||||
}
|
}
|
||||||
err = os.WriteFile(filepath.Join(wd, "lavender.public.key"), b.Bytes(), 0600)
|
err = os.WriteFile(filepath.Join(wd, "lavender.public.key"), b.Bytes(), 0600)
|
||||||
if err != nil && !errors.Is(err, os.ErrExist) {
|
if err != nil && !errors.Is(err, os.ErrExist) {
|
||||||
log.Fatal("[Lavender] Failed to save MJWT public key:", err)
|
log.Fatal("[PurpleTulip] Failed to save MJWT public key:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/1f349/tulip/cmd/purple-tulip/pages"
|
||||||
"github.com/1f349/tulip/issuer"
|
"github.com/1f349/tulip/issuer"
|
||||||
"github.com/1f349/tulip/purple-server/pages"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
@ -9,8 +9,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/1f349/cache"
|
"github.com/1f349/cache"
|
||||||
"github.com/1f349/mjwt"
|
"github.com/1f349/mjwt"
|
||||||
|
"github.com/1f349/tulip/cmd/purple-tulip/pages"
|
||||||
"github.com/1f349/tulip/issuer"
|
"github.com/1f349/tulip/issuer"
|
||||||
"github.com/1f349/tulip/purple-server/pages"
|
|
||||||
"github.com/1f349/tulip/utils"
|
"github.com/1f349/tulip/utils"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
@ -72,7 +72,7 @@ var testHttpServer = HttpServer{
|
|||||||
func init() {
|
func init() {
|
||||||
testHttpServer.conf.Store(&Conf{
|
testHttpServer.conf.Store(&Conf{
|
||||||
BaseUrl: lavenderDomain,
|
BaseUrl: lavenderDomain,
|
||||||
ServiceName: "Test Lavender Service",
|
ServiceName: "Test Purple Tulip Service",
|
||||||
})
|
})
|
||||||
testHttpServer.manager.Store(testManager)
|
testHttpServer.manager.Store(testManager)
|
||||||
testHttpServer.services.Store(&map[string]AllowedClient{
|
testHttpServer.services.Store(&map[string]AllowedClient{
|
||||||
@ -353,7 +353,7 @@ func TestFlowCallback(t *testing.T) {
|
|||||||
const p1 = `<!DOCTYPE html>
|
const p1 = `<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Test Lavender Service</title>
|
<title>Test Purple Tulip Service</title>
|
||||||
<link rel="stylesheet" href="/theme/style.css">
|
<link rel="stylesheet" href="/theme/style.css">
|
||||||
<script>
|
<script>
|
||||||
let loginData = {
|
let loginData = {
|
||||||
@ -372,7 +372,7 @@ func TestFlowCallback(t *testing.T) {
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<h1>Test Lavender Service</h1>
|
<h1>Test Purple Tulip Service</h1>
|
||||||
</header>
|
</header>
|
||||||
<main id="mainBody">Loading...</main>
|
<main id="mainBody">Loading...</main>
|
||||||
</body>
|
</body>
|
@ -86,13 +86,13 @@ func (h *HttpServer) finishTokenGenerateFlow(rw http.ResponseWriter, req *http.R
|
|||||||
marshal, err := json.Marshal(exchange)
|
marshal, err := json.Marshal(exchange)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to marshal exchange tokens", err)
|
fmt.Println("Failed to marshal exchange tokens", err)
|
||||||
http.Error(rw, "Internal red-server error", http.StatusInternalServerError)
|
http.Error(rw, "Internal server error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
oaepBytes, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, h.signer.PublicKey(), marshal, []byte("sso-exchange"))
|
oaepBytes, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, h.signer.PublicKey(), marshal, []byte("sso-exchange"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Failed to encrypt exchange tokens", err)
|
fmt.Println("Failed to encrypt exchange tokens", err)
|
||||||
http.Error(rw, "Internal red-server error", http.StatusInternalServerError)
|
http.Error(rw, "Internal server error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.SetCookie(rw, &http.Cookie{
|
http.SetCookie(rw, &http.Cookie{
|
@ -5,7 +5,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/1f349/cache"
|
"github.com/1f349/cache"
|
||||||
"github.com/1f349/mjwt"
|
"github.com/1f349/mjwt"
|
||||||
|
"github.com/1f349/tulip/database"
|
||||||
"github.com/1f349/tulip/issuer"
|
"github.com/1f349/tulip/issuer"
|
||||||
|
"github.com/1f349/tulip/oauth"
|
||||||
"github.com/1f349/tulip/theme"
|
"github.com/1f349/tulip/theme"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
@ -31,7 +33,7 @@ type flowStateData struct {
|
|||||||
target AllowedClient
|
target AllowedClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHttpServer(conf Conf, signer mjwt.Signer) *HttpServer {
|
func NewHttpServer(conf Conf, db *database.DB, controller *oauth.Controller, signer mjwt.Signer) *HttpServer {
|
||||||
r := httprouter.New()
|
r := httprouter.New()
|
||||||
|
|
||||||
// remove last slash from baseUrl
|
// remove last slash from baseUrl
|
@ -1,4 +1,4 @@
|
|||||||
package red_pages
|
package pages
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
@ -33,7 +33,7 @@ func LoadPages(wd string) (err error) {
|
|||||||
wdFs := os.DirFS(wwwDir)
|
wdFs := os.DirFS(wwwDir)
|
||||||
o = overlapfs.OverlapFS{A: wwwPages, B: wdFs}
|
o = overlapfs.OverlapFS{A: wwwPages, B: wdFs}
|
||||||
}
|
}
|
||||||
wwwTemplates, err = template.New("red-pages").Funcs(template.FuncMap{
|
wwwTemplates, err = template.New("pages").Funcs(template.FuncMap{
|
||||||
"emailHide": EmailHide,
|
"emailHide": EmailHide,
|
||||||
}).ParseFS(o, "*.go.html")
|
}).ParseFS(o, "*.go.html")
|
||||||
})
|
})
|
@ -1,4 +1,4 @@
|
|||||||
package red_pages
|
package pages
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
@ -9,10 +9,13 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/1f349/mjwt"
|
"github.com/1f349/mjwt"
|
||||||
|
clientStore "github.com/1f349/tulip/client-store"
|
||||||
|
"github.com/1f349/tulip/cmd/red-tulip/pages"
|
||||||
|
"github.com/1f349/tulip/cmd/red-tulip/server"
|
||||||
"github.com/1f349/tulip/database"
|
"github.com/1f349/tulip/database"
|
||||||
"github.com/1f349/tulip/mail/templates"
|
"github.com/1f349/tulip/mail/templates"
|
||||||
"github.com/1f349/tulip/red-pages"
|
"github.com/1f349/tulip/oauth"
|
||||||
"github.com/1f349/tulip/red-server"
|
"github.com/1f349/tulip/openid"
|
||||||
"github.com/1f349/violet/utils"
|
"github.com/1f349/violet/utils"
|
||||||
"github.com/MrMelon54/exit-reload"
|
"github.com/MrMelon54/exit-reload"
|
||||||
"github.com/google/subcommands"
|
"github.com/google/subcommands"
|
||||||
@ -46,39 +49,29 @@ func (s *serveCmd) Execute(_ context.Context, _ *flag.FlagSet, _ ...any) subcomm
|
|||||||
return subcommands.ExitUsageError
|
return subcommands.ExitUsageError
|
||||||
}
|
}
|
||||||
|
|
||||||
openConf, err := os.Open(s.configPath)
|
var conf server.Conf
|
||||||
|
err := loadConfig(s.configPath, &conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
log.Println("[RedTulip] Error: missing config file")
|
log.Println("[RedTulip] Error: missing config file")
|
||||||
} else {
|
} else {
|
||||||
log.Println("[RedTulip] Error: open config file: ", err)
|
log.Println("[RedTulip] Error: loading config file: ", err)
|
||||||
}
|
}
|
||||||
return subcommands.ExitFailure
|
return subcommands.ExitFailure
|
||||||
}
|
}
|
||||||
|
|
||||||
var config red_server.Conf
|
|
||||||
err = json.NewDecoder(openConf).Decode(&config)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("[RedTulip] Error: invalid config file: ", err)
|
|
||||||
return subcommands.ExitFailure
|
|
||||||
}
|
|
||||||
|
|
||||||
configPathAbs, err := filepath.Abs(s.configPath)
|
configPathAbs, err := filepath.Abs(s.configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("[RedTulip] Failed to get absolute config path")
|
log.Fatal("[RedTulip] Failed to get absolute config path")
|
||||||
}
|
}
|
||||||
wd := filepath.Dir(configPathAbs)
|
wd := filepath.Dir(configPathAbs)
|
||||||
normalLoad(config, wd)
|
|
||||||
return subcommands.ExitSuccess
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalLoad(startUp red_server.Conf, wd string) {
|
signer, err := mjwt.NewMJwtSignerFromFileOrCreate(conf.OtpIssuer, filepath.Join(wd, "red-tulip.key.pem"), rand.Reader, 4096)
|
||||||
signingKey, err := mjwt.NewMJwtSignerFromFileOrCreate(startUp.OtpIssuer, filepath.Join(wd, "tulip.key.pem"), rand.Reader, 4096)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("[Tulip] Failed to open signing key file:", err)
|
log.Fatal("[Tulip] Failed to open signing key file:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
db, err := database.Open(filepath.Join(wd, "red-red-tulip.db.sqlite"))
|
db, err := database.Open(filepath.Join(wd, "red-tulip.db.sqlite"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("[RedTulip] Failed to open database:", err)
|
log.Fatal("[RedTulip] Failed to open database:", err)
|
||||||
}
|
}
|
||||||
@ -88,34 +81,36 @@ func normalLoad(startUp red_server.Conf, wd string) {
|
|||||||
log.Fatal("[RedTulip] Failed check:", err)
|
log.Fatal("[RedTulip] Failed check:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = red_pages.LoadPages(wd); err != nil {
|
if err = pages.LoadPages(wd); err != nil {
|
||||||
log.Fatal("[RedTulip] Failed to load page templates:", err)
|
log.Fatal("[RedTulip] Failed to load page templates:", err)
|
||||||
}
|
}
|
||||||
if err := templates.LoadMailTemplates(wd); err != nil {
|
if err := templates.LoadMailTemplates(wd); err != nil {
|
||||||
log.Fatal("[RedTulip] Failed to load mail templates:", err)
|
log.Fatal("[RedTulip] Failed to load mail templates:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
srv := red_server.NewHttpServer(startUp, db, signingKey)
|
openIdConf := openid.GenConfig(conf.BaseUrl, []string{"openid", "name", "username", "profile", "email", "birthdate", "age", "zoneinfo", "locale"}, []string{"sub", "name", "preferred_username", "profile", "picture", "website", "email", "email_verified", "gender", "birthdate", "zoneinfo", "locale", "updated_at"})
|
||||||
log.Printf("[RedTulip] Starting HTTP red-server on '%s'\n", srv.Addr)
|
controller := oauth.NewOAuthController(signer, &server.RedAuthSource{DB: db}, clientStore.New(db), openIdConf)
|
||||||
|
|
||||||
|
srv := server.NewHttpServer(conf, db, controller, signer)
|
||||||
|
log.Printf("[RedTulip] Starting HTTP server on '%s'\n", srv.Addr)
|
||||||
go utils.RunBackgroundHttp("HTTP", srv)
|
go utils.RunBackgroundHttp("HTTP", srv)
|
||||||
|
|
||||||
exit_reload.ExitReload("RedTulip", func() {}, func() {
|
exit_reload.ExitReload("RedTulip", func() {}, func() {
|
||||||
// stop http red-server
|
// stop http server
|
||||||
_ = srv.Close()
|
_ = srv.Close()
|
||||||
_ = db.Close()
|
_ = db.Close()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return subcommands.ExitSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
func genHmacKey() []byte {
|
func loadConfig(configPath string, conf *server.Conf) error {
|
||||||
a := make([]byte, 32)
|
openConf, err := os.Open(configPath)
|
||||||
n, err := rand.Reader.Read(a)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("[RedTulip] Failed to generate HMAC key")
|
return err
|
||||||
}
|
}
|
||||||
if n != 32 {
|
|
||||||
log.Fatal("[RedTulip] Failed to generate HMAC key")
|
return json.NewDecoder(openConf).Decode(conf)
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkDbHasUser(db *database.DB) error {
|
func checkDbHasUser(db *database.DB) error {
|
||||||
|
45
cmd/red-tulip/server/auth-source.go
Normal file
45
cmd/red-tulip/server/auth-source.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/1f349/tulip/database"
|
||||||
|
"github.com/1f349/tulip/oauth"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RedAuthSource struct {
|
||||||
|
DB *database.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ oauth.AuthSource = &RedAuthSource{}
|
||||||
|
|
||||||
|
func (r *RedAuthSource) UserAuthorization(rw http.ResponseWriter, req *http.Request) (string, error) {
|
||||||
|
err := req.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
auth, err := internalAuthenticationHandler(rw, req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if auth.IsGuest() {
|
||||||
|
// handle redirecting to oauth
|
||||||
|
var q url.Values
|
||||||
|
switch req.Method {
|
||||||
|
case http.MethodPost:
|
||||||
|
q = req.PostForm
|
||||||
|
case http.MethodGet:
|
||||||
|
q = req.URL.Query()
|
||||||
|
default:
|
||||||
|
http.Error(rw, "405 Method Not Allowed", http.StatusMethodNotAllowed)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
redirectUrl := PrepareRedirectUrl("/login", &url.URL{Path: "/authorize", RawQuery: q.Encode()})
|
||||||
|
http.Redirect(rw, req, redirectUrl.String(), http.StatusFound)
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return auth.Data.ID.String(), nil
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package red_server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
@ -1,4 +1,4 @@
|
|||||||
package red_server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
@ -1,4 +1,4 @@
|
|||||||
package red_server
|
package server
|
||||||
|
|
||||||
import "github.com/1f349/tulip/mail"
|
import "github.com/1f349/tulip/mail"
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package red_server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/1f349/tulip/database"
|
"github.com/1f349/tulip/database"
|
@ -1,9 +1,9 @@
|
|||||||
package red_server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/1f349/tulip/cmd/red-tulip/pages"
|
||||||
"github.com/1f349/tulip/database"
|
"github.com/1f349/tulip/database"
|
||||||
"github.com/1f349/tulip/red-pages"
|
|
||||||
"github.com/1f349/tulip/utils"
|
"github.com/1f349/tulip/utils"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
@ -30,7 +30,7 @@ func (h *HttpServer) EditGet(rw http.ResponseWriter, _ *http.Request, _ httprout
|
|||||||
http.Error(rw, "Failed to save session", http.StatusInternalServerError)
|
http.Error(rw, "Failed to save session", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
red_pages.RenderPageTemplate(rw, "edit", map[string]any{
|
pages.RenderPageTemplate(rw, "edit", map[string]any{
|
||||||
"ServiceName": h.conf.ServiceName,
|
"ServiceName": h.conf.ServiceName,
|
||||||
"User": user,
|
"User": user,
|
||||||
"Nonce": lNonce,
|
"Nonce": lNonce,
|
@ -1,9 +1,9 @@
|
|||||||
package red_server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/1f349/tulip/cmd/red-tulip/pages"
|
||||||
"github.com/1f349/tulip/database"
|
"github.com/1f349/tulip/database"
|
||||||
"github.com/1f349/tulip/red-pages"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -13,7 +13,7 @@ func (h *HttpServer) Home(rw http.ResponseWriter, req *http.Request, _ httproute
|
|||||||
rw.Header().Set("Content-Type", "text/html")
|
rw.Header().Set("Content-Type", "text/html")
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
if auth.IsGuest() {
|
if auth.IsGuest() {
|
||||||
red_pages.RenderPageTemplate(rw, "index-guest", map[string]any{
|
pages.RenderPageTemplate(rw, "index-guest", map[string]any{
|
||||||
"ServiceName": h.conf.ServiceName,
|
"ServiceName": h.conf.ServiceName,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@ -41,7 +41,7 @@ func (h *HttpServer) Home(rw http.ResponseWriter, req *http.Request, _ httproute
|
|||||||
}) {
|
}) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
red_pages.RenderPageTemplate(rw, "index", map[string]any{
|
pages.RenderPageTemplate(rw, "index", map[string]any{
|
||||||
"ServiceName": h.conf.ServiceName,
|
"ServiceName": h.conf.ServiceName,
|
||||||
"Auth": auth,
|
"Auth": auth,
|
||||||
"User": userWithName,
|
"User": userWithName,
|
@ -1,4 +1,4 @@
|
|||||||
package red_server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
@ -8,8 +8,8 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/1f349/tulip/cmd/red-tulip/pages"
|
||||||
"github.com/1f349/tulip/database"
|
"github.com/1f349/tulip/database"
|
||||||
"github.com/1f349/tulip/red-pages"
|
|
||||||
"github.com/emersion/go-message/mail"
|
"github.com/emersion/go-message/mail"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
@ -46,7 +46,7 @@ func (h *HttpServer) LoginGet(rw http.ResponseWriter, req *http.Request, _ httpr
|
|||||||
|
|
||||||
rw.Header().Set("Content-Type", "text/html")
|
rw.Header().Set("Content-Type", "text/html")
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
red_pages.RenderPageTemplate(rw, "login", map[string]any{
|
pages.RenderPageTemplate(rw, "login", map[string]any{
|
||||||
"ServiceName": h.conf.ServiceName,
|
"ServiceName": h.conf.ServiceName,
|
||||||
"Redirect": req.URL.Query().Get("redirect"),
|
"Redirect": req.URL.Query().Get("redirect"),
|
||||||
"Mismatch": req.URL.Query().Get("mismatch"),
|
"Mismatch": req.URL.Query().Get("mismatch"),
|
||||||
@ -70,7 +70,7 @@ func (h *HttpServer) LoginPost(rw http.ResponseWriter, req *http.Request, _ http
|
|||||||
loginMismatch = 1
|
loginMismatch = 1
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
http.Error(rw, "Internal red-server error", http.StatusInternalServerError)
|
http.Error(rw, "Internal server error", http.StatusInternalServerError)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
|||||||
package red_server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/1f349/tulip/cmd/red-tulip/pages"
|
||||||
"github.com/1f349/tulip/database"
|
"github.com/1f349/tulip/database"
|
||||||
"github.com/1f349/tulip/red-pages"
|
|
||||||
"github.com/emersion/go-message/mail"
|
"github.com/emersion/go-message/mail"
|
||||||
"github.com/go-session/session"
|
"github.com/go-session/session"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@ -67,7 +67,7 @@ func (h *HttpServer) MailPassword(rw http.ResponseWriter, req *http.Request, par
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
red_pages.RenderPageTemplate(rw, "reset-password", map[string]any{
|
pages.RenderPageTemplate(rw, "reset-password", map[string]any{
|
||||||
"ServiceName": h.conf.ServiceName,
|
"ServiceName": h.conf.ServiceName,
|
||||||
})
|
})
|
||||||
}
|
}
|
@ -1,8 +1,8 @@
|
|||||||
package red_server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/1f349/tulip/cmd/red-tulip/pages"
|
||||||
"github.com/1f349/tulip/database"
|
"github.com/1f349/tulip/database"
|
||||||
"github.com/1f349/tulip/red-pages"
|
|
||||||
"github.com/go-oauth2/oauth2/v4"
|
"github.com/go-oauth2/oauth2/v4"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
@ -58,7 +58,7 @@ func (h *HttpServer) ManageAppsGet(rw http.ResponseWriter, req *http.Request, _
|
|||||||
validEdit:
|
validEdit:
|
||||||
rw.Header().Set("Content-Type", "text/html")
|
rw.Header().Set("Content-Type", "text/html")
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
red_pages.RenderPageTemplate(rw, "manage-apps", m)
|
pages.RenderPageTemplate(rw, "manage-apps", m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HttpServer) ManageAppsPost(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, auth UserAuth) {
|
func (h *HttpServer) ManageAppsPost(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, auth UserAuth) {
|
@ -1,9 +1,9 @@
|
|||||||
package red_server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/1f349/tulip/cmd/red-tulip/pages"
|
||||||
"github.com/1f349/tulip/database"
|
"github.com/1f349/tulip/database"
|
||||||
"github.com/1f349/tulip/red-pages"
|
|
||||||
"github.com/emersion/go-message/mail"
|
"github.com/emersion/go-message/mail"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
@ -66,7 +66,7 @@ func (h *HttpServer) ManageUsersGet(rw http.ResponseWriter, req *http.Request, _
|
|||||||
validEdit:
|
validEdit:
|
||||||
rw.Header().Set("Content-Type", "text/html")
|
rw.Header().Set("Content-Type", "text/html")
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
red_pages.RenderPageTemplate(rw, "manage-users", m)
|
pages.RenderPageTemplate(rw, "manage-users", m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HttpServer) ManageUsersPost(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, auth UserAuth) {
|
func (h *HttpServer) ManageUsersPost(rw http.ResponseWriter, req *http.Request, _ httprouter.Params, auth UserAuth) {
|
@ -1,8 +1,8 @@
|
|||||||
package red_server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/1f349/tulip/cmd/red-tulip/pages"
|
||||||
"github.com/1f349/tulip/database"
|
"github.com/1f349/tulip/database"
|
||||||
"github.com/1f349/tulip/red-pages"
|
|
||||||
"github.com/1f349/tulip/utils"
|
"github.com/1f349/tulip/utils"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -100,7 +100,7 @@ func (h *HttpServer) authorizeEndpoint(rw http.ResponseWriter, req *http.Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
red_pages.RenderPageTemplate(rw, "oauth-authorize", map[string]any{
|
pages.RenderPageTemplate(rw, "oauth-authorize", map[string]any{
|
||||||
"ServiceName": h.conf.ServiceName,
|
"ServiceName": h.conf.ServiceName,
|
||||||
"AppName": appName,
|
"AppName": appName,
|
||||||
"AppDomain": appDomain,
|
"AppDomain": appDomain,
|
||||||
@ -143,34 +143,3 @@ func (h *HttpServer) authorizeEndpoint(rw http.ResponseWriter, req *http.Request
|
|||||||
parsedRedirect.RawQuery = q.Encode()
|
parsedRedirect.RawQuery = q.Encode()
|
||||||
http.Redirect(rw, req, parsedRedirect.String(), http.StatusFound)
|
http.Redirect(rw, req, parsedRedirect.String(), http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HttpServer) oauthUserAuthorization(rw http.ResponseWriter, req *http.Request) (string, error) {
|
|
||||||
err := req.ParseForm()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
auth, err := internalAuthenticationHandler(rw, req)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if auth.IsGuest() {
|
|
||||||
// handle redirecting to oauth
|
|
||||||
var q url.Values
|
|
||||||
switch req.Method {
|
|
||||||
case http.MethodPost:
|
|
||||||
q = req.PostForm
|
|
||||||
case http.MethodGet:
|
|
||||||
q = req.URL.Query()
|
|
||||||
default:
|
|
||||||
http.Error(rw, "405 Method Not Allowed", http.StatusMethodNotAllowed)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
redirectUrl := PrepareRedirectUrl("/login", &url.URL{Path: "/authorize", RawQuery: q.Encode()})
|
|
||||||
http.Redirect(rw, req, redirectUrl.String(), http.StatusFound)
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
return auth.Data.ID.String(), nil
|
|
||||||
}
|
|
@ -1,10 +1,10 @@
|
|||||||
package red_server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"github.com/1f349/tulip/cmd/red-tulip/pages"
|
||||||
"github.com/1f349/tulip/database"
|
"github.com/1f349/tulip/database"
|
||||||
"github.com/1f349/tulip/red-pages"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
"github.com/skip2/go-qrcode"
|
"github.com/skip2/go-qrcode"
|
||||||
@ -21,7 +21,7 @@ func (h *HttpServer) LoginOtpGet(rw http.ResponseWriter, req *http.Request, _ ht
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
red_pages.RenderPageTemplate(rw, "login-otp", map[string]any{
|
pages.RenderPageTemplate(rw, "login-otp", map[string]any{
|
||||||
"ServiceName": h.conf.ServiceName,
|
"ServiceName": h.conf.ServiceName,
|
||||||
"Redirect": req.URL.Query().Get("redirect"),
|
"Redirect": req.URL.Query().Get("redirect"),
|
||||||
})
|
})
|
||||||
@ -80,7 +80,7 @@ func (h *HttpServer) EditOtpPost(rw http.ResponseWriter, req *http.Request, _ ht
|
|||||||
if req.Method == http.MethodPost && req.FormValue("remove") == "1" {
|
if req.Method == http.MethodPost && req.FormValue("remove") == "1" {
|
||||||
if !req.Form.Has("code") {
|
if !req.Form.Has("code") {
|
||||||
// render page
|
// render page
|
||||||
red_pages.RenderPageTemplate(rw, "remove-otp", map[string]any{
|
pages.RenderPageTemplate(rw, "remove-otp", map[string]any{
|
||||||
"ServiceName": h.conf.ServiceName,
|
"ServiceName": h.conf.ServiceName,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
@ -154,7 +154,7 @@ func (h *HttpServer) EditOtpPost(rw http.ResponseWriter, req *http.Request, _ ht
|
|||||||
}
|
}
|
||||||
|
|
||||||
// render page
|
// render page
|
||||||
red_pages.RenderPageTemplate(rw, "edit-otp", map[string]any{
|
pages.RenderPageTemplate(rw, "edit-otp", map[string]any{
|
||||||
"ServiceName": h.conf.ServiceName,
|
"ServiceName": h.conf.ServiceName,
|
||||||
"OtpQr": template.URL("data:qrImg/png;base64," + base64.StdEncoding.EncodeToString(qrBuf.Bytes())),
|
"OtpQr": template.URL("data:qrImg/png;base64," + base64.StdEncoding.EncodeToString(qrBuf.Bytes())),
|
||||||
"QrWidth": qrWidth,
|
"QrWidth": qrWidth,
|
@ -1,4 +1,4 @@
|
|||||||
package red_server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -8,16 +8,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/1f349/cache"
|
"github.com/1f349/cache"
|
||||||
"github.com/1f349/mjwt"
|
"github.com/1f349/mjwt"
|
||||||
clientStore "github.com/1f349/tulip/client-store"
|
|
||||||
"github.com/1f349/tulip/database"
|
"github.com/1f349/tulip/database"
|
||||||
"github.com/1f349/tulip/openid"
|
"github.com/1f349/tulip/oauth"
|
||||||
"github.com/1f349/tulip/theme"
|
"github.com/1f349/tulip/theme"
|
||||||
scope2 "github.com/1f349/tulip/utils"
|
|
||||||
"github.com/go-oauth2/oauth2/v4/errors"
|
"github.com/go-oauth2/oauth2/v4/errors"
|
||||||
"github.com/go-oauth2/oauth2/v4/generates"
|
|
||||||
"github.com/go-oauth2/oauth2/v4/manage"
|
"github.com/go-oauth2/oauth2/v4/manage"
|
||||||
"github.com/go-oauth2/oauth2/v4/server"
|
"github.com/go-oauth2/oauth2/v4/server"
|
||||||
"github.com/go-oauth2/oauth2/v4/store"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
"log"
|
"log"
|
||||||
@ -52,7 +48,7 @@ type mailLinkKey struct {
|
|||||||
data uuid.UUID
|
data uuid.UUID
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHttpServer(conf Conf, db *database.DB, signingKey mjwt.Signer) *http.Server {
|
func NewHttpServer(conf Conf, db *database.DB, oauthController *oauth.Controller, signingKey mjwt.Signer) *http.Server {
|
||||||
r := httprouter.New()
|
r := httprouter.New()
|
||||||
|
|
||||||
// remove last slash from baseUrl
|
// remove last slash from baseUrl
|
||||||
@ -63,8 +59,7 @@ func NewHttpServer(conf Conf, db *database.DB, signingKey mjwt.Signer) *http.Ser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
openIdConf := openid.GenConfig(conf.BaseUrl, []string{"openid", "name", "username", "profile", "email", "birthdate", "age", "zoneinfo", "locale"}, []string{"sub", "name", "preferred_username", "profile", "picture", "website", "email", "email_verified", "gender", "birthdate", "zoneinfo", "locale", "updated_at"})
|
openIdBytes, err := json.Marshal(oauthController.OidConf)
|
||||||
openIdBytes, err := json.Marshal(openIdConf)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("Failed to generate OpenID configuration:", err)
|
log.Fatalln("Failed to generate OpenID configuration:", err)
|
||||||
}
|
}
|
||||||
@ -82,39 +77,6 @@ func NewHttpServer(conf Conf, db *database.DB, signingKey mjwt.Signer) *http.Ser
|
|||||||
mailLinkCache: cache.New[mailLinkKey, uuid.UUID](),
|
mailLinkCache: cache.New[mailLinkKey, uuid.UUID](),
|
||||||
}
|
}
|
||||||
|
|
||||||
oauthManager.SetAuthorizeCodeTokenCfg(manage.DefaultAuthorizeCodeTokenCfg)
|
|
||||||
oauthManager.MustTokenStorage(store.NewMemoryTokenStore())
|
|
||||||
oauthManager.MapAccessGenerate(generates.NewAccessGenerate())
|
|
||||||
oauthManager.MapClientStorage(clientStore.New(db))
|
|
||||||
|
|
||||||
oauthSrv.SetResponseErrorHandler(func(re *errors.Response) {
|
|
||||||
log.Printf("Response error: %#v\n", re)
|
|
||||||
})
|
|
||||||
oauthSrv.SetClientInfoHandler(func(req *http.Request) (clientID, clientSecret string, err error) {
|
|
||||||
cId, cSecret, err := server.ClientBasicHandler(req)
|
|
||||||
if cId == "" && cSecret == "" {
|
|
||||||
cId, cSecret, err = server.ClientFormHandler(req)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
return cId, cSecret, nil
|
|
||||||
})
|
|
||||||
oauthSrv.SetUserAuthorizationHandler(hs.oauthUserAuthorization)
|
|
||||||
oauthSrv.SetAuthorizeScopeHandler(func(rw http.ResponseWriter, req *http.Request) (scope string, err error) {
|
|
||||||
var form url.Values
|
|
||||||
if req.Method == http.MethodPost {
|
|
||||||
form = req.PostForm
|
|
||||||
} else {
|
|
||||||
form = req.URL.Query()
|
|
||||||
}
|
|
||||||
a := form.Get("scope")
|
|
||||||
if !scope2.ScopesExist(a) {
|
|
||||||
return "", errInvalidScope
|
|
||||||
}
|
|
||||||
return a, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
r.GET("/.well-known/openid-configuration", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
r.GET("/.well-known/openid-configuration", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
_, _ = rw.Write(openIdBytes)
|
_, _ = rw.Write(openIdBytes)
|
||||||
@ -164,18 +126,18 @@ func NewHttpServer(conf Conf, db *database.DB, signingKey mjwt.Signer) *http.Ser
|
|||||||
r.POST("/mail/password", hs.MailPasswordPost)
|
r.POST("/mail/password", hs.MailPasswordPost)
|
||||||
r.GET("/mail/delete/:code", hs.MailDelete)
|
r.GET("/mail/delete/:code", hs.MailDelete)
|
||||||
|
|
||||||
// edit profile red-pages
|
// edit profile pages
|
||||||
r.GET("/edit", hs.RequireAuthentication(hs.EditGet))
|
r.GET("/edit", hs.RequireAuthentication(hs.EditGet))
|
||||||
r.POST("/edit", hs.RequireAuthentication(hs.EditPost))
|
r.POST("/edit", hs.RequireAuthentication(hs.EditPost))
|
||||||
r.POST("/edit/otp", hs.RequireAuthentication(hs.EditOtpPost))
|
r.POST("/edit/otp", hs.RequireAuthentication(hs.EditOtpPost))
|
||||||
|
|
||||||
// management red-pages
|
// management pages
|
||||||
r.GET("/manage/apps", hs.RequireAdminAuthentication(hs.ManageAppsGet))
|
r.GET("/manage/apps", hs.RequireAdminAuthentication(hs.ManageAppsGet))
|
||||||
r.POST("/manage/apps", hs.RequireAdminAuthentication(hs.ManageAppsPost))
|
r.POST("/manage/apps", hs.RequireAdminAuthentication(hs.ManageAppsPost))
|
||||||
r.GET("/manage/users", hs.RequireAdminAuthentication(hs.ManageUsersGet))
|
r.GET("/manage/users", hs.RequireAdminAuthentication(hs.ManageUsersGet))
|
||||||
r.POST("/manage/users", hs.RequireAdminAuthentication(hs.ManageUsersPost))
|
r.POST("/manage/users", hs.RequireAdminAuthentication(hs.ManageUsersPost))
|
||||||
|
|
||||||
// oauth red-pages
|
// oauth pages
|
||||||
r.GET("/authorize", hs.RequireAuthentication(hs.authorizeEndpoint))
|
r.GET("/authorize", hs.RequireAuthentication(hs.authorizeEndpoint))
|
||||||
r.POST("/authorize", hs.RequireAuthentication(hs.authorizeEndpoint))
|
r.POST("/authorize", hs.RequireAuthentication(hs.authorizeEndpoint))
|
||||||
r.POST("/token", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
r.POST("/token", func(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
|
@ -1,4 +1,4 @@
|
|||||||
package red_server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@ -13,7 +13,7 @@ import (
|
|||||||
type Mail struct {
|
type Mail struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Tls bool `json:"tls"`
|
Tls bool `json:"tls"`
|
||||||
Server string `json:"red-server"`
|
Server string `json:"server"`
|
||||||
From FromAddress `json:"from"`
|
From FromAddress `json:"from"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
74
oauth/controller.go
Normal file
74
oauth/controller.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package oauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/1f349/mjwt"
|
||||||
|
"github.com/1f349/tulip/openid"
|
||||||
|
scope2 "github.com/1f349/tulip/utils"
|
||||||
|
"github.com/go-oauth2/oauth2/v4"
|
||||||
|
"github.com/go-oauth2/oauth2/v4/errors"
|
||||||
|
"github.com/go-oauth2/oauth2/v4/manage"
|
||||||
|
"github.com/go-oauth2/oauth2/v4/server"
|
||||||
|
"github.com/go-oauth2/oauth2/v4/store"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errInvalidScope = errors.New("missing required scope")
|
||||||
|
|
||||||
|
type Controller struct {
|
||||||
|
baseUrl string
|
||||||
|
mgr *manage.Manager
|
||||||
|
srv *server.Server
|
||||||
|
OidConf openid.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthSource interface {
|
||||||
|
UserAuthorization(rw http.ResponseWriter, req *http.Request) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOAuthController(signer mjwt.Signer, source AuthSource, clientStore oauth2.ClientStore, oidConf openid.Config) *Controller {
|
||||||
|
c := &Controller{
|
||||||
|
// remove last slash from baseUrl
|
||||||
|
baseUrl: strings.TrimSuffix(oidConf.Issuer, "/"),
|
||||||
|
mgr: manage.NewDefaultManager(),
|
||||||
|
OidConf: oidConf,
|
||||||
|
}
|
||||||
|
c.srv = server.NewServer(server.NewConfig(), c.mgr)
|
||||||
|
|
||||||
|
c.mgr.SetAuthorizeCodeTokenCfg(manage.DefaultAuthorizeCodeTokenCfg)
|
||||||
|
c.mgr.MustTokenStorage(store.NewMemoryTokenStore())
|
||||||
|
c.mgr.MapAccessGenerate(NewJWTAccessGenerate(signer))
|
||||||
|
c.mgr.MapClientStorage(clientStore)
|
||||||
|
|
||||||
|
c.srv.SetResponseErrorHandler(func(re *errors.Response) {
|
||||||
|
log.Printf("Response error: %#v\n", re)
|
||||||
|
})
|
||||||
|
c.srv.SetClientInfoHandler(func(req *http.Request) (clientID, clientSecret string, err error) {
|
||||||
|
cId, cSecret, err := server.ClientBasicHandler(req)
|
||||||
|
if cId == "" && cSecret == "" {
|
||||||
|
cId, cSecret, err = server.ClientFormHandler(req)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
return cId, cSecret, nil
|
||||||
|
})
|
||||||
|
c.srv.SetUserAuthorizationHandler(source.UserAuthorization)
|
||||||
|
c.srv.SetAuthorizeScopeHandler(func(rw http.ResponseWriter, req *http.Request) (scope string, err error) {
|
||||||
|
var form url.Values
|
||||||
|
if req.Method == http.MethodPost {
|
||||||
|
form = req.PostForm
|
||||||
|
} else {
|
||||||
|
form = req.URL.Query()
|
||||||
|
}
|
||||||
|
a := form.Get("scope")
|
||||||
|
if !scope2.ScopesExist(a) {
|
||||||
|
return "", errInvalidScope
|
||||||
|
}
|
||||||
|
return a, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
35
oauth/jwt.go
Normal file
35
oauth/jwt.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package oauth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"github.com/1f349/mjwt"
|
||||||
|
"github.com/1f349/mjwt/auth"
|
||||||
|
"github.com/go-oauth2/oauth2/v4"
|
||||||
|
"github.com/golang-jwt/jwt/v4"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JWTAccessGenerate struct {
|
||||||
|
signer mjwt.Signer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewJWTAccessGenerate(signer mjwt.Signer) *JWTAccessGenerate {
|
||||||
|
return &JWTAccessGenerate{signer}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ oauth2.AccessGenerate = &JWTAccessGenerate{}
|
||||||
|
|
||||||
|
func (j JWTAccessGenerate) Token(ctx context.Context, data *oauth2.GenerateBasic, isGenRefresh bool) (access, refresh string, err error) {
|
||||||
|
access, err = j.signer.GenerateJwt(data.UserID, "", jwt.ClaimStrings{data.Client.GetID()}, data.TokenInfo.GetAccessExpiresIn(), auth.AccessTokenClaims{})
|
||||||
|
|
||||||
|
if isGenRefresh {
|
||||||
|
t := uuid.NewHash(sha256.New(), uuid.New(), []byte(access), 5).String()
|
||||||
|
refresh = base64.URLEncoding.EncodeToString([]byte(t))
|
||||||
|
refresh = strings.ToUpper(strings.TrimRight(refresh, "="))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
@ -84,7 +84,7 @@
|
|||||||
|
|
||||||
function doThisThing() {
|
function doThisThing() {
|
||||||
if (currentLoginPopup) currentLoginPopup.close();
|
if (currentLoginPopup) currentLoginPopup.close();
|
||||||
currentLoginPopup = popupCenterScreen(ssoService + '/popup?origin=' + encodeURIComponent(location.origin), 'Login with Lavender', 500, 500, false);
|
currentLoginPopup = popupCenterScreen(ssoService + '/popup?origin=' + encodeURIComponent(location.origin), 'Login with Purple Tulip', 500, 500, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshAllTokens() {
|
async function refreshAllTokens() {
|
||||||
|
BIN
purple-tulip
Executable file
BIN
purple-tulip
Executable file
Binary file not shown.
Loading…
Reference in New Issue
Block a user