mirror of
https://github.com/1f349/lavender.git
synced 2024-12-22 07:34:06 +00:00
some stuff
This commit is contained in:
parent
a81aa0458a
commit
33c7ac9b06
@ -1,3 +1,7 @@
|
||||
# Lavender
|
||||
|
||||
An authentication source for multiple login services to be used with a frontend single page application.
|
||||
A login service with OpenID, OAuth2 and SSO support.
|
||||
|
||||
Login via third-party services.
|
||||
|
||||
Enables easy use of a single authentication source for a network of services.
|
||||
|
16
conf/conf.go
16
conf/conf.go
@ -6,12 +6,12 @@ import (
|
||||
)
|
||||
|
||||
type Conf struct {
|
||||
Listen string `yaml:"listen"`
|
||||
BaseUrl string `yaml:"baseUrl"`
|
||||
ServiceName string `yaml:"serviceName"`
|
||||
Issuer string `yaml:"issuer"`
|
||||
Kid string `yaml:"kid"`
|
||||
Namespace string `yaml:"namespace"`
|
||||
Mail mail.Mail `yaml:"mail"`
|
||||
SsoServices []issuer.SsoConfig `yaml:"ssoServices"`
|
||||
Listen string `yaml:"listen"`
|
||||
BaseUrl string `yaml:"baseUrl"`
|
||||
ServiceName string `yaml:"serviceName"`
|
||||
Issuer string `yaml:"issuer"`
|
||||
Kid string `yaml:"kid"`
|
||||
Namespace string `yaml:"namespace"`
|
||||
Mail mail.Mail `yaml:"mail"`
|
||||
SsoServices map[string]issuer.SsoConfig `yaml:"ssoServices"`
|
||||
}
|
||||
|
47
config.example.yml
Normal file
47
config.example.yml
Normal file
@ -0,0 +1,47 @@
|
||||
# address to listen on
|
||||
listen: ':9090'
|
||||
|
||||
# url for absolute links to the login service
|
||||
baseUrl: 'http://localhost:9090'
|
||||
|
||||
# human-readable service name
|
||||
serviceName: 'Example Login'
|
||||
|
||||
# name of the login issuer
|
||||
issuer: 'id.example.com'
|
||||
|
||||
# id of the private key in the keystore
|
||||
kid: 'fdd2eb6d-b469-44c8-b15b-495bcf34dae4'
|
||||
|
||||
# defines the domain part of login name `user@example.com`
|
||||
namespace: 'example.com'
|
||||
|
||||
# configure automated emails
|
||||
mail:
|
||||
name: 'Example Login'
|
||||
tls: true
|
||||
server: 'smtp.example.com:465'
|
||||
from: 'Example Login <noreply@id.example.com>'
|
||||
username: 'noreply@id.example.com'
|
||||
password: '#####'
|
||||
|
||||
# enable local accounts
|
||||
localLogin: true
|
||||
|
||||
# configure SSO login services
|
||||
ssoServices:
|
||||
example.net:
|
||||
addr: 'https://example.net'
|
||||
client:
|
||||
id: 'dcea4be8-dff4-49d2-a5e6-c1b202403714'
|
||||
secret: '#####'
|
||||
scopes:
|
||||
- openid
|
||||
- name
|
||||
- username
|
||||
- profile
|
||||
- email
|
||||
- birthdate
|
||||
- age
|
||||
- zoneinfo
|
||||
- locale
|
46
database/types/userlocale.go
Normal file
46
database/types/userlocale.go
Normal file
@ -0,0 +1,46 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
var (
|
||||
_ sql.Scanner = &UserLocale{}
|
||||
_ driver.Valuer = &UserLocale{}
|
||||
_ json.Marshaler = &UserLocale{}
|
||||
_ json.Unmarshaler = &UserLocale{}
|
||||
)
|
||||
|
||||
type UserLocale struct{ language.Tag }
|
||||
|
||||
func (l *UserLocale) Scan(src any) error {
|
||||
s, ok := src.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported Scan, storing driver.Value type %T into type %T", src, l)
|
||||
}
|
||||
lang, err := language.Parse(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.Tag = lang
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l UserLocale) Value() (driver.Value, error) {
|
||||
return l.Tag.String(), nil
|
||||
}
|
||||
|
||||
func (l UserLocale) MarshalJSON() ([]byte, error) { return json.Marshal(l.Tag.String()) }
|
||||
|
||||
func (l *UserLocale) UnmarshalJSON(bytes []byte) error {
|
||||
var a string
|
||||
err := json.Unmarshal(bytes, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return l.Scan(a)
|
||||
}
|
12
database/types/userlocale_test.go
Normal file
12
database/types/userlocale_test.go
Normal file
@ -0,0 +1,12 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/text/language"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUserLocale_MarshalJSON(t *testing.T) {
|
||||
assert.Equal(t, "\"en-US\"", encode(UserLocale{language.AmericanEnglish}))
|
||||
assert.Equal(t, "\"en-GB\"", encode(UserLocale{language.BritishEnglish}))
|
||||
}
|
46
database/types/userpronoun.go
Normal file
46
database/types/userpronoun.go
Normal file
@ -0,0 +1,46 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/mrmelon54/pronouns"
|
||||
)
|
||||
|
||||
var (
|
||||
_ sql.Scanner = &UserPronoun{}
|
||||
_ driver.Valuer = &UserPronoun{}
|
||||
_ json.Marshaler = &UserPronoun{}
|
||||
_ json.Unmarshaler = &UserPronoun{}
|
||||
)
|
||||
|
||||
type UserPronoun struct{ pronouns.Pronoun }
|
||||
|
||||
func (p *UserPronoun) Scan(src any) error {
|
||||
s, ok := src.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported Scan, storing driver.Value type %T into type %T", src, p)
|
||||
}
|
||||
pro, err := pronouns.FindPronoun(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Pronoun = pro
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p UserPronoun) Value() (driver.Value, error) {
|
||||
return p.Pronoun.String(), nil
|
||||
}
|
||||
|
||||
func (p UserPronoun) MarshalJSON() ([]byte, error) { return json.Marshal(p.Pronoun.String()) }
|
||||
|
||||
func (p *UserPronoun) UnmarshalJSON(bytes []byte) error {
|
||||
var a string
|
||||
err := json.Unmarshal(bytes, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.Scan(a)
|
||||
}
|
15
database/types/userpronoun_test.go
Normal file
15
database/types/userpronoun_test.go
Normal file
@ -0,0 +1,15 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/mrmelon54/pronouns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUserPronoun_MarshalJSON(t *testing.T) {
|
||||
assert.Equal(t, "\"they/them\"", encode(UserPronoun{pronouns.TheyThem}))
|
||||
assert.Equal(t, "\"he/him\"", encode(UserPronoun{pronouns.HeHim}))
|
||||
assert.Equal(t, "\"she/her\"", encode(UserPronoun{pronouns.SheHer}))
|
||||
assert.Equal(t, "\"it/its\"", encode(UserPronoun{pronouns.ItIts}))
|
||||
assert.Equal(t, "\"one/one's\"", encode(UserPronoun{pronouns.OneOnes}))
|
||||
}
|
27
database/types/userrole.go
Normal file
27
database/types/userrole.go
Normal file
@ -0,0 +1,27 @@
|
||||
package types
|
||||
|
||||
import "fmt"
|
||||
|
||||
type UserRole int64
|
||||
|
||||
const (
|
||||
RoleMember UserRole = iota
|
||||
RoleAdmin
|
||||
RoleToDelete
|
||||
)
|
||||
|
||||
func (r UserRole) String() string {
|
||||
switch r {
|
||||
case RoleMember:
|
||||
return "Member"
|
||||
case RoleAdmin:
|
||||
return "Admin"
|
||||
case RoleToDelete:
|
||||
return "ToDelete"
|
||||
}
|
||||
return fmt.Sprintf("UserRole{ %d }", r)
|
||||
}
|
||||
|
||||
func (r UserRole) IsValid() bool {
|
||||
return r == RoleMember || r == RoleAdmin
|
||||
}
|
48
database/types/userzone.go
Normal file
48
database/types/userzone.go
Normal file
@ -0,0 +1,48 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
_ sql.Scanner = &UserZone{}
|
||||
_ driver.Valuer = &UserZone{}
|
||||
_ json.Marshaler = &UserZone{}
|
||||
_ json.Unmarshaler = &UserZone{}
|
||||
)
|
||||
|
||||
type UserZone struct{ *time.Location }
|
||||
|
||||
func (l *UserZone) Scan(src any) error {
|
||||
s, ok := src.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported Scan, storing driver.Value type %T into type %T", src, l)
|
||||
}
|
||||
loc, err := time.LoadLocation(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.Location = loc
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l UserZone) Value() (driver.Value, error) {
|
||||
return l.Location.String(), nil
|
||||
}
|
||||
|
||||
func (l UserZone) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(l.Location.String())
|
||||
}
|
||||
|
||||
func (l *UserZone) UnmarshalJSON(bytes []byte) error {
|
||||
var a string
|
||||
err := json.Unmarshal(bytes, &a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return l.Scan(a)
|
||||
}
|
14
database/types/userzone_test.go
Normal file
14
database/types/userzone_test.go
Normal file
@ -0,0 +1,14 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestUserZone_MarshalJSON(t *testing.T) {
|
||||
location, err := time.LoadLocation("Europe/London")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "\"Europe/London\"", encode(UserZone{location}))
|
||||
assert.Equal(t, "\"UTC\"", encode(UserZone{time.UTC}))
|
||||
}
|
11
database/types/utils_test.go
Normal file
11
database/types/utils_test.go
Normal file
@ -0,0 +1,11 @@
|
||||
package types
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
func encode(data any) string {
|
||||
j, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(j)
|
||||
}
|
1
go.mod
1
go.mod
@ -19,6 +19,7 @@ require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/mrmelon54/pronouns v1.0.3
|
||||
github.com/spf13/afero v1.11.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golang.org/x/crypto v0.26.0
|
||||
|
2
go.sum
2
go.sum
@ -120,6 +120,8 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
|
||||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
||||
github.com/mrmelon54/pronouns v1.0.3 h1:VJqOnNxIw44q0dRJrBEvOCkKPYGvPYcNRKwPtLildXg=
|
||||
github.com/mrmelon54/pronouns v1.0.3/go.mod h1:VF6iGNf72tIokVE78GasPXvxlFUwGib7QFZOfpDNn18=
|
||||
github.com/mrmelon54/rescheduler v0.0.3 h1:TrkJL6S7PKvXuo1mvdgRgsILA/pk5L1lrXhV/q7IEzQ=
|
||||
github.com/mrmelon54/rescheduler v0.0.3/go.mod h1:q415n6W1xcePPP5Rix6FOiADgcN66BYMyNOsFnNyoWQ=
|
||||
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||
|
@ -12,20 +12,20 @@ type Manager struct {
|
||||
m map[string]*WellKnownOIDC
|
||||
}
|
||||
|
||||
func NewManager(services []SsoConfig) (*Manager, error) {
|
||||
func NewManager(services map[string]SsoConfig) (*Manager, error) {
|
||||
l := &Manager{m: make(map[string]*WellKnownOIDC)}
|
||||
for _, i := range services {
|
||||
if !isValidNamespace.MatchString(i.Namespace) {
|
||||
return nil, fmt.Errorf("invalid namespace: %s", i.Namespace)
|
||||
for namespace, ssoService := range services {
|
||||
if !isValidNamespace.MatchString(namespace) {
|
||||
return nil, fmt.Errorf("invalid namespace: %s", namespace)
|
||||
}
|
||||
|
||||
conf, err := i.FetchConfig()
|
||||
conf, err := ssoService.FetchConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// save by namespace
|
||||
l.m[i.Namespace] = conf
|
||||
l.m[namespace] = conf
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
@ -17,9 +17,8 @@ var httpGet = http.Get
|
||||
// SsoConfig is the base URL for an OAUTH/OPENID/SSO login service
|
||||
// The path `/.well-known/openid-configuration` should be available
|
||||
type SsoConfig struct {
|
||||
Addr utils.JsonUrl `json:"addr"` // https://login.example.com
|
||||
Namespace string `json:"namespace"` // example.com
|
||||
Client SsoConfigClient `json:"client"`
|
||||
Addr utils.JsonUrl `json:"addr"` // https://login.example.com
|
||||
Client SsoConfigClient `json:"client"`
|
||||
}
|
||||
|
||||
type SsoConfigClient struct {
|
||||
|
13
sqlc.yaml
13
sqlc.yaml
@ -8,3 +8,16 @@ sql:
|
||||
package: "database"
|
||||
out: "database"
|
||||
emit_json_tags: true
|
||||
overrides:
|
||||
- column: "users.password"
|
||||
go_type: "github.com/1f349/tulip/password.HashString"
|
||||
- column: "users.birthdate"
|
||||
go_type: "github.com/hardfinhq/go-date.NullDate"
|
||||
- column: "users.role"
|
||||
go_type: "github.com/1f349/tulip/database/types.UserRole"
|
||||
- column: "users.pronouns"
|
||||
go_type: "github.com/1f349/tulip/database/types.UserPronoun"
|
||||
- column: "users.zoneinfo"
|
||||
go_type: "github.com/1f349/tulip/database/types.UserZone"
|
||||
- column: "users.locale"
|
||||
go_type: "github.com/1f349/tulip/database/types.UserLocale"
|
||||
|
Loading…
Reference in New Issue
Block a user