Start merging lavender and tulip

This commit is contained in:
Melon 2024-08-19 22:37:30 +01:00
parent a6c0751061
commit a81aa0458a
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
20 changed files with 525 additions and 8 deletions

View File

@ -2,6 +2,7 @@ package conf
import (
"github.com/1f349/lavender/issuer"
"github.com/1f349/lavender/mail"
)
type Conf struct {
@ -10,5 +11,7 @@ type Conf struct {
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"`
}

12
go.mod
View File

@ -6,8 +6,12 @@ require (
github.com/1f349/cache v0.0.3
github.com/1f349/mjwt v0.4.1
github.com/1f349/overlapfs v0.0.1
github.com/1f349/tulip v0.0.0-20240725211619-6b19e2d4ca63
github.com/charmbracelet/log v0.4.0
github.com/cloudflare/tableflip v1.2.3
github.com/emersion/go-message v0.18.1
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43
github.com/emersion/go-smtp v0.21.3
github.com/go-oauth2/oauth2/v4 v4.5.2
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/golang-migrate/migrate/v4 v4.17.1
@ -17,7 +21,9 @@ require (
github.com/mattn/go-sqlite3 v1.14.22
github.com/spf13/afero v1.11.0
github.com/stretchr/testify v1.9.0
golang.org/x/crypto v0.26.0
golang.org/x/oauth2 v0.22.0
golang.org/x/text v0.17.0
gopkg.in/yaml.v3 v3.0.1
)
@ -31,7 +37,7 @@ require (
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/klauspost/compress v1.17.9 // indirect
@ -53,10 +59,8 @@ require (
github.com/tidwall/rtred v0.1.2 // indirect
github.com/tidwall/tinyqueue v0.1.1 // indirect
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect
golang.org/x/net v0.27.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/text v0.17.0 // indirect
)

39
go.sum
View File

@ -7,6 +7,8 @@ github.com/1f349/overlapfs v0.0.1 h1:LAxBolrXFAgU0yqZtXg/C/aaPq3eoQSPpBc49BHuTp0
github.com/1f349/overlapfs v0.0.1/go.mod h1:I6aItQycr7nrzplmfNXp/QF9tTmKRSgY3fXmu/7Ky2o=
github.com/1f349/rsa-helper v0.0.2 h1:N/fLQqg5wrjIzG6G4zdwa5Xcv9/jIPutCls9YekZr9U=
github.com/1f349/rsa-helper v0.0.2/go.mod h1:VUQ++1tYYhYrXeOmVFkQ82BegR24HQEJHl5lHbjg7yg=
github.com/1f349/tulip v0.0.0-20240725211619-6b19e2d4ca63 h1:jPg+0bgKD5kY7yQtRZqeba+BGKFE51evGvwewZwa7Xc=
github.com/1f349/tulip v0.0.0-20240725211619-6b19e2d4ca63/go.mod h1:1zFQhcbgiyPSWHVMp0cXJjmd6FhasP5bf5tWS4ZK61A=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
@ -27,6 +29,13 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emersion/go-message v0.18.1 h1:tfTxIoXFSFRwWaZsgnqS1DSZuGpYGzSmCZD8SK3QA2E=
github.com/emersion/go-message v0.18.1/go.mod h1:XpJyL70LwRvq2a8rVbHXikPgKj8+aI0kGdHlg16ibYA=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43 h1:hH4PQfOndHDlpzYfLAAfl63E8Le6F2+EL/cdhlkyRJY=
github.com/emersion/go-sasl v0.0.0-20231106173351-e73c9f7bad43/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-smtp v0.21.3 h1:7uVwagE8iPYE48WhNsng3RRpCUpFvNl39JGNSIyGVMY=
github.com/emersion/go-smtp v0.21.3/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
@ -72,8 +81,8 @@ github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@ -193,29 +202,40 @@ github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FB
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI=
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -233,19 +253,30 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=

104
lists/locales.go Normal file
View File

@ -0,0 +1,104 @@
package lists
import (
"golang.org/x/text/language"
"golang.org/x/text/language/display"
"sync"
)
var (
localeOnce sync.Once
localeNames []struct{ Value, Label string }
)
func ListLocale() []struct{ Value, Label string } {
localeOnce.Do(func() {
localeNames = make([]struct{ Value, Label string }, len(localeList))
for i := range localeList {
localeNames[i] = struct{ Value, Label string }{Value: localeList[i].String(), Label: display.Self.Name(localeList[i])}
}
})
return localeNames
}
var localeList = []language.Tag{
language.Afrikaans,
language.Amharic,
language.Arabic,
language.ModernStandardArabic,
language.Azerbaijani,
language.Bulgarian,
language.Bengali,
language.Catalan,
language.Czech,
language.Danish,
language.German,
language.Greek,
language.English,
language.AmericanEnglish,
language.BritishEnglish,
language.Spanish,
language.EuropeanSpanish,
language.LatinAmericanSpanish,
language.Estonian,
language.Persian,
language.Finnish,
language.Filipino,
language.French,
language.CanadianFrench,
language.Gujarati,
language.Hebrew,
language.Hindi,
language.Croatian,
language.Hungarian,
language.Armenian,
language.Indonesian,
language.Icelandic,
language.Italian,
language.Japanese,
language.Georgian,
language.Kazakh,
language.Khmer,
language.Kannada,
language.Korean,
language.Kirghiz,
language.Lao,
language.Lithuanian,
language.Latvian,
language.Macedonian,
language.Malayalam,
language.Mongolian,
language.Marathi,
language.Malay,
language.Burmese,
language.Nepali,
language.Dutch,
language.Norwegian,
language.Punjabi,
language.Polish,
language.Portuguese,
language.BrazilianPortuguese,
language.EuropeanPortuguese,
language.Romanian,
language.Russian,
language.Sinhala,
language.Slovak,
language.Slovenian,
language.Albanian,
language.Serbian,
language.SerbianLatin,
language.Swedish,
language.Swahili,
language.Tamil,
language.Telugu,
language.Thai,
language.Turkish,
language.Ukrainian,
language.Urdu,
language.Uzbek,
language.Vietnamese,
language.Chinese,
language.SimplifiedChinese,
language.TraditionalChinese,
language.Zulu,
}

15
lists/locales_test.go Normal file
View File

@ -0,0 +1,15 @@
package lists
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestListLocale(t *testing.T) {
locales := ListLocale()
assert.True(t, len(locales) > 4)
assert.Equal(t, struct{ Value, Label string }{Value: "af", Label: "Afrikaans"}, locales[0])
assert.Equal(t, struct{ Value, Label string }{Value: "am", Label: "አማርኛ"}, locales[1])
assert.Equal(t, struct{ Value, Label string }{Value: "zh-Hant", Label: "繁體中文"}, locales[len(locales)-2])
assert.Equal(t, struct{ Value, Label string }{Value: "zu", Label: "isiZulu"}, locales[len(locales)-1])
}

53
lists/zoneinfo.go Normal file
View File

@ -0,0 +1,53 @@
package lists
import (
"os"
"path/filepath"
"sort"
"strings"
"sync"
)
var (
zoneDirs = []string{
// Update path according to your OS
"/usr/share/zoneinfo/",
"/usr/share/lib/zoneinfo/",
"/usr/lib/locale/TZ/",
}
zoneInfoOnce sync.Once
zoneNames []string
)
func ListZoneInfo() []string {
zoneInfoOnce.Do(func() {
zoneNames = make([]string, 0)
for _, zoneDir := range zoneDirs {
zoneNames = append(zoneNames, FindTimeZoneFiles(zoneDir)...)
}
sort.Strings(zoneNames)
})
return zoneNames
}
func FindTimeZoneFiles(zoneDir string) []string {
dArr := make([]string, 0)
dArr = append(dArr, "")
arr := make([]string, 0)
for i := 0; i < len(dArr); i++ {
dir := dArr[i]
files, _ := os.ReadDir(filepath.Join(zoneDir, dir))
for _, f := range files {
if f.Name() != strings.ToUpper(f.Name()[:1])+f.Name()[1:] {
continue
}
if f.IsDir() {
dArr = append(dArr, filepath.Join(dir, f.Name()))
} else {
arr = append(arr, filepath.Join(dir, f.Name()))
}
}
}
return arr
}

15
lists/zoneinfo_test.go Normal file
View File

@ -0,0 +1,15 @@
package lists
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestListZoneInfo(t *testing.T) {
zoneinfos := ListZoneInfo()
assert.True(t, len(zoneinfos) > 4)
assert.Equal(t, "Africa/Abidjan", zoneinfos[0])
assert.Equal(t, "Africa/Accra", zoneinfos[1])
assert.Equal(t, "WET", zoneinfos[len(zoneinfos)-2])
assert.Equal(t, "Zulu", zoneinfos[len(zoneinfos)-1])
}

26
mail/from-address.go Normal file
View File

@ -0,0 +1,26 @@
package mail
import (
"encoding/json"
"github.com/emersion/go-message/mail"
)
type FromAddress struct {
*mail.Address
}
var _ json.Unmarshaler = &FromAddress{}
func (f *FromAddress) UnmarshalJSON(b []byte) error {
var a string
err := json.Unmarshal(b, &a)
if err != nil {
return err
}
address, err := mail.ParseAddress(a)
if err != nil {
return err
}
f.Address = address
return nil
}

96
mail/mail.go Normal file
View File

@ -0,0 +1,96 @@
package mail
import (
"bytes"
"github.com/emersion/go-message/mail"
"github.com/emersion/go-sasl"
"github.com/emersion/go-smtp"
"io"
"net"
"time"
)
type Mail struct {
Name string `json:"name"`
Tls bool `json:"tls"`
Server string `json:"server"`
From FromAddress `json:"from"`
Username string `json:"username"`
Password string `json:"password"`
}
func (m *Mail) loginInfo() sasl.Client {
return sasl.NewPlainClient("", m.Username, m.Password)
}
func (m *Mail) mailCall(to []string, r io.Reader) error {
host, _, err := net.SplitHostPort(m.Server)
if err != nil {
return err
}
if m.Tls {
return smtp.SendMailTLS(m.Server, m.loginInfo(), m.From.String(), to, r)
}
if host == "localhost" || host == "127.0.0.1" {
// internals of smtp.SendMail without STARTTLS for localhost testing
dial, err := smtp.Dial(m.Server)
if err != nil {
return err
}
err = dial.Auth(m.loginInfo())
if err != nil {
return err
}
return dial.SendMail(m.From.String(), to, r)
}
return smtp.SendMail(m.Server, m.loginInfo(), m.From.String(), to, r)
}
func (m *Mail) SendMail(subject string, to []*mail.Address, htmlBody, textBody io.Reader) error {
// generate the email in this template
buf := new(bytes.Buffer)
// setup mail headers
var h mail.Header
h.SetDate(time.Now())
h.SetSubject(subject)
h.SetAddressList("From", []*mail.Address{m.From.Address})
h.SetAddressList("To", to)
h.Set("Content-Type", "multipart/alternative")
// setup html and text alternative headers
var hHtml, hTxt mail.InlineHeader
hHtml.Set("Content-Type", "text/html; charset=utf-8")
hTxt.Set("Content-Type", "text/plain; charset=utf-8")
createWriter, err := mail.CreateWriter(buf, h)
if err != nil {
return err
}
inline, err := createWriter.CreateInline()
if err != nil {
return err
}
partHtml, err := inline.CreatePart(hHtml)
if err != nil {
return err
}
if _, err := io.Copy(partHtml, htmlBody); err != nil {
return err
}
partTxt, err := inline.CreatePart(hTxt)
if err != nil {
return err
}
if _, err := io.Copy(partTxt, textBody); err != nil {
return err
}
// convert all to addresses to strings
toStr := make([]string, len(to))
for i := range toStr {
toStr[i] = to[i].String()
}
return m.mailCall(toStr, buf)
}

18
mail/send-template.go Normal file
View File

@ -0,0 +1,18 @@
package mail
import (
"bytes"
"fmt"
"github.com/1f349/lavender/mail/templates"
"github.com/emersion/go-message/mail"
)
func (m *Mail) SendEmailTemplate(templateName, subject, nameOfUser string, to *mail.Address, data map[string]any) error {
var bufHtml, bufTxt bytes.Buffer
templates.RenderMailTemplate(&bufHtml, &bufTxt, templateName, map[string]any{
"ServiceName": m.Name,
"Name": nameOfUser,
"Data": data,
})
return m.SendMail(fmt.Sprintf("%s - %s", subject, m.Name), []*mail.Address{to}, &bufHtml, &bufTxt)
}

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<body>
<p>Hello, {{.Name}}</p>
<p>Your account with {{.ServiceName}} has been disabled and marked for deletion.</p>
<p>Your account will be fully deleted within 48-hours.</p>
<p>You will no longer receive emails from {{.ServiceName}}, unless your email address is used to set up an account.</p>
<p>Regards,<br/>{{.ServiceName}}</p>
</body>
</html>

View File

@ -0,0 +1,10 @@
Hello, {{.Name}}
Your account with {{.ServiceName}} has been disabled and marked for deletion.
Your account will be fully deleted within 48-hours.
You will no longer receive emails from {{.ServiceName}}, unless your email address is used to set up an account.
Regards,
{{.ServiceName}}

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<body>
<p>Hello, {{.Name}}</p>
<p>Your email address has been registered with {{.ServiceName}} by an administrator.</p>
<p>Please open this link to verify your email address and <a href="{{.Data.RegisterUrl}}" target="_blank">register your account</a></p>
<p>If you did not wish to register for {{.ServiceName}}, then your email has probably been used by mistake.</p>
<p>If the link above is not used to register an account, then no further contact will be made from {{.ServiceName}} and your email address will be deleted from our systems within a 48-hour period.</p>
<p>Regards,<br/>{{.ServiceName}}</p>
</body>
</html>

View File

@ -0,0 +1,12 @@
Hello, {{.Name}}
Your email address has been registered with {{.ServiceName}} by an administrator.
Please open this link to verify your email address and register your account {{.Data.RegisterUrl}}
If you did not wish to register for {{.ServiceName}}, then your email has probably been used by mistake.
If the link above is not used to register an account, then no further contact will be made from {{.ServiceName}} and your email address will be deleted from our systems within a 48-hour period.
Regards,
{{.ServiceName}}

View File

@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<body>
<p>Hello, {{.Name}}</p>
<p>Please open this link to <a href="{{.Data.ResetUrl}}" target="_blank">reset your password</a></p>
<p>This link is valid for 10 minutes.</p>
<p>Regards,<br/>{{.ServiceName}}</p>
</body>
</html>

View File

@ -0,0 +1,8 @@
Hello, {{.Name}}
Please open this link to reset your password: {{.Data.ResetUrl}}
This link is valid for 10 minutes.
Regards,
{{.ServiceName}}

View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<body>
<p>Hello, {{.Name}}</p>
<p>Please open this link to <a href="{{.Data.VerifyUrl}}" target="_blank">verify your email address</a></p>
<p>This link is valid for 10 minutes.</p>
<p>If you did not create an account with {{.ServiceName}} then please ignore this email and the account will be deleted within a 48-hour period.</p>
<p>Regards,<br/>{{.ServiceName}}</p>
</body>
</html>

View File

@ -0,0 +1,10 @@
Hello, {{.Name}}
Please open this link to verify your email address: {{.Data.VerifyUrl}}
This link is valid for 10 minutes.
If you did not create an account with {{.ServiceName}} then please ignore this email and the account will be deleted within a 48-hour period.
Regards,
{{.ServiceName}}

View File

@ -0,0 +1,55 @@
package templates
import (
"embed"
"errors"
"github.com/1f349/overlapfs"
"github.com/1f349/tulip/logger"
htmlTemplate "html/template"
"io"
"io/fs"
"os"
"path/filepath"
"sync"
textTemplate "text/template"
)
var (
//go:embed *.go.html *.go.txt
embeddedTemplates embed.FS
mailHtmlTemplates *htmlTemplate.Template
mailTextTemplates *textTemplate.Template
loadOnce sync.Once
)
func LoadMailTemplates(wd string) (err error) {
loadOnce.Do(func() {
var o fs.FS = embeddedTemplates
if wd != "" {
mailDir := filepath.Join(wd, "mail-templates")
err = os.Mkdir(mailDir, os.ModePerm)
if err != nil && !errors.Is(err, os.ErrExist) {
return
}
wdFs := os.DirFS(mailDir)
o = overlapfs.OverlapFS{A: embeddedTemplates, B: wdFs}
}
mailHtmlTemplates, err = htmlTemplate.New("mail").ParseFS(o, "*.go.html")
if err != nil {
return
}
mailTextTemplates, err = textTemplate.New("mail").ParseFS(o, "*.go.txt")
})
return
}
func RenderMailTemplate(wrHtml, wrTxt io.Writer, name string, data any) {
err := mailHtmlTemplates.ExecuteTemplate(wrHtml, name+".go.html", data)
if err != nil {
logger.Logger.Warn("Failed to render mail html", "name", name, "err", err)
}
err = mailTextTemplates.ExecuteTemplate(wrTxt, name+".go.txt", data)
if err != nil {
logger.Logger.Warn("Failed to render mail text", "name", name, "err", err)
}
}

17
password/password.go Normal file
View File

@ -0,0 +1,17 @@
package password
import (
"golang.org/x/crypto/bcrypt"
)
// HashString is used to represent a string containing a password hash
type HashString string
func HashPassword(password string) (HashString, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
return HashString(bytes), err
}
func CheckPasswordHash(hash HashString, password string) error {
return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
}