Implement Ory authentication
Some checks reported errors
continuous-integration/drone/push Build was killed
Some checks reported errors
continuous-integration/drone/push Build was killed
This commit is contained in:
parent
c1744940f5
commit
9beff44f09
28
api/auth.go
28
api/auth.go
|
@ -30,6 +30,8 @@ import (
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
ory "github.com/ory/client-go"
|
||||||
|
|
||||||
"git.happydns.org/happyDomain/actions"
|
"git.happydns.org/happyDomain/actions"
|
||||||
"git.happydns.org/happyDomain/config"
|
"git.happydns.org/happyDomain/config"
|
||||||
|
@ -139,7 +141,7 @@ func requireLogin(opts *config.Options, c *gin.Context, msg string) {
|
||||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"errmsg": msg})
|
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"errmsg": msg})
|
||||||
}
|
}
|
||||||
|
|
||||||
func authMiddleware(opts *config.Options, optional bool) gin.HandlerFunc {
|
func authMiddleware(opts *config.Options, o *ory.APIClient, optional bool) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
var token string
|
var token string
|
||||||
|
|
||||||
|
@ -150,6 +152,30 @@ func authMiddleware(opts *config.Options, optional bool) gin.HandlerFunc {
|
||||||
token = flds[1]
|
token = flds[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if o != nil {
|
||||||
|
cookies := c.Request.Header.Get("Cookie")
|
||||||
|
|
||||||
|
session, _, err := o.FrontendAPI.ToSession(c.Request.Context()).Cookie(cookies).TokenizeAs("jwt_happydomain").Execute()
|
||||||
|
if !((err != nil && session == nil) || (err == nil && !*session.Active)) {
|
||||||
|
if session.Tokenized != nil {
|
||||||
|
token = *session.Tokenized
|
||||||
|
setCookie(opts, c, token)
|
||||||
|
} else if session.Identity != nil && len(session.Identity.VerifiableAddresses) >= 0 {
|
||||||
|
uid, err := uuid.Parse(session.Identity.Id)
|
||||||
|
if err != nil {
|
||||||
|
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to parse user UUID"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tmp := [16]byte(uid)
|
||||||
|
_, token, _ = completeAuth(opts, c, UserProfile{
|
||||||
|
UserId: tmp[:],
|
||||||
|
Email: session.Identity.VerifiableAddresses[0].Value,
|
||||||
|
EmailVerified: session.Identity.VerifiableAddresses[0].Verified,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Stop here if there is no cookie
|
// Stop here if there is no cookie
|
||||||
if len(token) == 0 {
|
if len(token) == 0 {
|
||||||
if optional {
|
if optional {
|
||||||
|
|
|
@ -23,6 +23,7 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
ory "github.com/ory/client-go"
|
||||||
|
|
||||||
"git.happydns.org/happyDomain/config"
|
"git.happydns.org/happyDomain/config"
|
||||||
)
|
)
|
||||||
|
@ -47,10 +48,10 @@ import (
|
||||||
// @name Authorization
|
// @name Authorization
|
||||||
// @description Description for what is this security definition being used
|
// @description Description for what is this security definition being used
|
||||||
|
|
||||||
func DeclareRoutes(cfg *config.Options, router *gin.Engine) {
|
func DeclareRoutes(cfg *config.Options, o *ory.APIClient, router *gin.Engine) {
|
||||||
apiRoutes := router.Group("/api")
|
apiRoutes := router.Group("/api")
|
||||||
|
|
||||||
declareAuthenticationRoutes(cfg, apiRoutes)
|
declareAuthenticationRoutes(cfg, o, apiRoutes)
|
||||||
declareProviderSpecsRoutes(apiRoutes)
|
declareProviderSpecsRoutes(apiRoutes)
|
||||||
declareResolverRoutes(apiRoutes)
|
declareResolverRoutes(apiRoutes)
|
||||||
declareServiceSpecsRoutes(apiRoutes)
|
declareServiceSpecsRoutes(apiRoutes)
|
||||||
|
@ -58,7 +59,7 @@ func DeclareRoutes(cfg *config.Options, router *gin.Engine) {
|
||||||
DeclareVersionRoutes(apiRoutes)
|
DeclareVersionRoutes(apiRoutes)
|
||||||
|
|
||||||
apiAuthRoutes := router.Group("/api")
|
apiAuthRoutes := router.Group("/api")
|
||||||
apiAuthRoutes.Use(authMiddleware(cfg, false))
|
apiAuthRoutes.Use(authMiddleware(cfg, o, false))
|
||||||
|
|
||||||
declareDomainsRoutes(cfg, apiAuthRoutes)
|
declareDomainsRoutes(cfg, apiAuthRoutes)
|
||||||
declareProvidersRoutes(cfg, apiAuthRoutes)
|
declareProvidersRoutes(cfg, apiAuthRoutes)
|
||||||
|
|
|
@ -31,6 +31,7 @@ import (
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
ory "github.com/ory/client-go"
|
||||||
|
|
||||||
"git.happydns.org/happyDomain/config"
|
"git.happydns.org/happyDomain/config"
|
||||||
"git.happydns.org/happyDomain/model"
|
"git.happydns.org/happyDomain/model"
|
||||||
|
@ -39,7 +40,7 @@ import (
|
||||||
|
|
||||||
const NO_AUTH_ACCOUNT = "_no_auth"
|
const NO_AUTH_ACCOUNT = "_no_auth"
|
||||||
|
|
||||||
func declareAuthenticationRoutes(opts *config.Options, router *gin.RouterGroup) {
|
func declareAuthenticationRoutes(opts *config.Options, o *ory.APIClient, router *gin.RouterGroup) {
|
||||||
router.POST("/auth", func(c *gin.Context) {
|
router.POST("/auth", func(c *gin.Context) {
|
||||||
checkAuth(opts, c)
|
checkAuth(opts, c)
|
||||||
})
|
})
|
||||||
|
@ -48,7 +49,7 @@ func declareAuthenticationRoutes(opts *config.Options, router *gin.RouterGroup)
|
||||||
})
|
})
|
||||||
|
|
||||||
apiAuthRoutes := router.Group("/auth")
|
apiAuthRoutes := router.Group("/auth")
|
||||||
apiAuthRoutes.Use(authMiddleware(opts, true))
|
apiAuthRoutes.Use(authMiddleware(opts, o, true))
|
||||||
|
|
||||||
apiAuthRoutes.GET("", func(c *gin.Context) {
|
apiAuthRoutes.GET("", func(c *gin.Context) {
|
||||||
if _, exists := c.Get("MySession"); exists {
|
if _, exists := c.Get("MySession"); exists {
|
||||||
|
@ -106,7 +107,7 @@ func displayNotAuthToken(opts *config.Options, c *gin.Context) *UserClaims {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
claims, err := completeAuth(opts, c, UserProfile{
|
claims, _, err := completeAuth(opts, c, UserProfile{
|
||||||
UserId: []byte{0},
|
UserId: []byte{0},
|
||||||
Email: NO_AUTH_ACCOUNT,
|
Email: NO_AUTH_ACCOUNT,
|
||||||
EmailVerified: true,
|
EmailVerified: true,
|
||||||
|
@ -199,7 +200,7 @@ func checkAuth(opts *config.Options, c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
claims, err := completeAuth(opts, c, UserProfile{
|
claims, _, err := completeAuth(opts, c, UserProfile{
|
||||||
UserId: user.Id,
|
UserId: user.Id,
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
EmailVerified: user.EmailVerification != nil,
|
EmailVerified: user.EmailVerification != nil,
|
||||||
|
@ -222,12 +223,12 @@ func checkAuth(opts *config.Options, c *gin.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func completeAuth(opts *config.Options, c *gin.Context, userprofile UserProfile) (*UserClaims, error) {
|
func completeAuth(opts *config.Options, c *gin.Context, userprofile UserProfile) (*UserClaims, string, error) {
|
||||||
// Issue a new JWT token
|
// Issue a new JWT token
|
||||||
jti := make([]byte, 16)
|
jti := make([]byte, 16)
|
||||||
_, err := rand.Read(jti)
|
_, err := rand.Read(jti)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to read enough random bytes: %w", err)
|
return nil, "", fmt.Errorf("unable to read enough random bytes: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
iat := jwt.NewNumericDate(time.Now())
|
iat := jwt.NewNumericDate(time.Now())
|
||||||
|
@ -243,9 +244,15 @@ func completeAuth(opts *config.Options, c *gin.Context, userprofile UserProfile)
|
||||||
|
|
||||||
token, err := jwtToken.SignedString([]byte(opts.JWTSecretKey))
|
token, err := jwtToken.SignedString([]byte(opts.JWTSecretKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to sign user claims: %w", err)
|
return nil, "", fmt.Errorf("unable to sign user claims: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setCookie(opts, c, token)
|
||||||
|
|
||||||
|
return claims, token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setCookie(opts *config.Options, c *gin.Context, token string) {
|
||||||
c.SetCookie(
|
c.SetCookie(
|
||||||
COOKIE_NAME, // name
|
COOKIE_NAME, // name
|
||||||
token, // value
|
token, // value
|
||||||
|
@ -255,6 +262,4 @@ func completeAuth(opts *config.Options, c *gin.Context, userprofile UserProfile)
|
||||||
opts.DevProxy == "" && opts.ExternalURL.URL.Scheme != "http", // secure
|
opts.DevProxy == "" && opts.ExternalURL.URL.Scheme != "http", // secure
|
||||||
true, // httpOnly
|
true, // httpOnly
|
||||||
)
|
)
|
||||||
|
|
||||||
return claims, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ func (o *Options) declareFlags() {
|
||||||
flag.BoolVar(&o.NoAuth, "no-auth", false, "Disable user access control, use default account")
|
flag.BoolVar(&o.NoAuth, "no-auth", false, "Disable user access control, use default account")
|
||||||
flag.Var(&o.JWTSecretKey, "jwt-secret-key", "Secret key used to verify JWT authentication tokens (a random secret is used if undefined)")
|
flag.Var(&o.JWTSecretKey, "jwt-secret-key", "Secret key used to verify JWT authentication tokens (a random secret is used if undefined)")
|
||||||
flag.Var(&o.ExternalAuth, "external-auth", "Base URL to use for login and registration (use embedded forms if left empty)")
|
flag.Var(&o.ExternalAuth, "external-auth", "Base URL to use for login and registration (use embedded forms if left empty)")
|
||||||
|
flag.Var(&o.OryKratosServer, "ory-kratos-server", "URL to the Ory Kratos server (default: none, use classical auth)")
|
||||||
|
|
||||||
// Others flags are declared in some other files likes sources, storages, ... when they need specials configurations
|
// Others flags are declared in some other files likes sources, storages, ... when they need specials configurations
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,9 @@ type Options struct {
|
||||||
|
|
||||||
// JWTSecretKey stores the private key to sign and verify JWT tokens.
|
// JWTSecretKey stores the private key to sign and verify JWT tokens.
|
||||||
JWTSecretKey JWTSecretKey
|
JWTSecretKey JWTSecretKey
|
||||||
|
|
||||||
|
// OryKratosServer is the URL to the authentication server.
|
||||||
|
OryKratosServer URL
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildURL appends the given url to the absolute ExternalURL.
|
// BuildURL appends the given url to the absolute ExternalURL.
|
||||||
|
|
3
go.mod
3
go.mod
|
@ -10,8 +10,10 @@ require (
|
||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/go-mail/mail v2.3.1+incompatible
|
github.com/go-mail/mail v2.3.1+incompatible
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.0
|
github.com/golang-jwt/jwt/v5 v5.2.0
|
||||||
|
github.com/google/uuid v1.5.0
|
||||||
github.com/miekg/dns v1.1.57
|
github.com/miekg/dns v1.1.57
|
||||||
github.com/nullrocks/identicon v0.0.0-20180626043057-7875f45b0022
|
github.com/nullrocks/identicon v0.0.0-20180626043057-7875f45b0022
|
||||||
|
github.com/ory/client-go v1.4.7
|
||||||
github.com/ovh/go-ovh v1.4.3
|
github.com/ovh/go-ovh v1.4.3
|
||||||
github.com/swaggo/files v1.0.1
|
github.com/swaggo/files v1.0.1
|
||||||
github.com/swaggo/gin-swagger v1.6.0
|
github.com/swaggo/gin-swagger v1.6.0
|
||||||
|
@ -85,7 +87,6 @@ require (
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
github.com/google/s2a-go v0.1.7 // indirect
|
github.com/google/s2a-go v0.1.7 // indirect
|
||||||
github.com/google/uuid v1.4.0 // indirect
|
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -212,6 +212,8 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||||
|
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
||||||
|
@ -328,6 +330,8 @@ github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
|
||||||
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
|
||||||
github.com/oracle/oci-go-sdk/v32 v32.0.0 h1:SSbzrQO3WRcPJEZ8+b3SFPYsPtkFM96clqrp03lrwbU=
|
github.com/oracle/oci-go-sdk/v32 v32.0.0 h1:SSbzrQO3WRcPJEZ8+b3SFPYsPtkFM96clqrp03lrwbU=
|
||||||
github.com/oracle/oci-go-sdk/v32 v32.0.0/go.mod h1:aZc4jC59IuNP3cr5y1nj555QvwojMX2nMJaBiozuuEs=
|
github.com/oracle/oci-go-sdk/v32 v32.0.0/go.mod h1:aZc4jC59IuNP3cr5y1nj555QvwojMX2nMJaBiozuuEs=
|
||||||
|
github.com/ory/client-go v1.4.7 h1:uWPGGM5zVwpSBfcDIhvA6D+bu2YB7zF4STtpAvzkOco=
|
||||||
|
github.com/ory/client-go v1.4.7/go.mod h1:DfrTIlME7tgrdgpn4UN07s4OJ1SwzHfrkz+C6C0Lbm0=
|
||||||
github.com/ovh/go-ovh v1.4.3 h1:Gs3V823zwTFpzgGLZNI6ILS4rmxZgJwJCz54Er9LwD0=
|
github.com/ovh/go-ovh v1.4.3 h1:Gs3V823zwTFpzgGLZNI6ILS4rmxZgJwJCz54Er9LwD0=
|
||||||
github.com/ovh/go-ovh v1.4.3/go.mod h1:AkPXVtgwB6xlKblMjRKJJmjRp+ogrE7fz2lVgcQY8SY=
|
github.com/ovh/go-ovh v1.4.3/go.mod h1:AkPXVtgwB6xlKblMjRKJJmjRp+ogrE7fz2lVgcQY8SY=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
|
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
ory "github.com/ory/client-go"
|
||||||
|
|
||||||
"git.happydns.org/happyDomain/api"
|
"git.happydns.org/happyDomain/api"
|
||||||
"git.happydns.org/happyDomain/config"
|
"git.happydns.org/happyDomain/config"
|
||||||
|
@ -37,6 +38,7 @@ import (
|
||||||
type App struct {
|
type App struct {
|
||||||
router *gin.Engine
|
router *gin.Engine
|
||||||
cfg *config.Options
|
cfg *config.Options
|
||||||
|
ory *ory.APIClient
|
||||||
srv *http.Server
|
srv *http.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,14 +51,20 @@ func NewApp(cfg *config.Options) App {
|
||||||
router := gin.New()
|
router := gin.New()
|
||||||
router.Use(gin.Logger(), gin.Recovery())
|
router.Use(gin.Logger(), gin.Recovery())
|
||||||
|
|
||||||
api.DeclareRoutes(cfg, router)
|
|
||||||
ui.DeclareRoutes(cfg, router)
|
|
||||||
|
|
||||||
app := App{
|
app := App{
|
||||||
router: router,
|
router: router,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.OryKratosServer.URL != nil {
|
||||||
|
c := ory.NewConfiguration()
|
||||||
|
c.Servers = ory.ServerConfigurations{{URL: cfg.OryKratosServer.URL.String()}}
|
||||||
|
app.ory = ory.NewAPIClient(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
api.DeclareRoutes(cfg, app.ory, router)
|
||||||
|
ui.DeclareRoutes(cfg, router)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
@ -63,6 +64,10 @@ func DeclareRoutes(cfg *config.Options, router *gin.Engine) {
|
||||||
CustomHeadHTML += fmt.Sprintf(`<script type="text/javascript">window.msg_header = { text: %q, color: %q };</script>`, MsgHeaderText, MsgHeaderColor)
|
CustomHeadHTML += fmt.Sprintf(`<script type="text/javascript">window.msg_header = { text: %q, color: %q };</script>`, MsgHeaderText, MsgHeaderColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.OryKratosServer.URL != nil {
|
||||||
|
CustomHeadHTML += fmt.Sprintf(`<script type="text/javascript">window.happydomain_ory_kratos_url = %q;</script>`, cfg.OryKratosServer.URL.String())
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.DevProxy != "" {
|
if cfg.DevProxy != "" {
|
||||||
router.GET("/.svelte-kit/*_", serveOrReverse("", cfg))
|
router.GET("/.svelte-kit/*_", serveOrReverse("", cfg))
|
||||||
router.GET("/node_modules/*_", serveOrReverse("", cfg))
|
router.GET("/node_modules/*_", serveOrReverse("", cfg))
|
||||||
|
|
|
@ -77,9 +77,6 @@
|
||||||
bind:this={formElm}
|
bind:this={formElm}
|
||||||
on:submit|preventDefault={goSendLink}
|
on:submit|preventDefault={goSendLink}
|
||||||
>
|
>
|
||||||
<p class="text-center">
|
|
||||||
{$t('email.recover')}.
|
|
||||||
</p>
|
|
||||||
<Row>
|
<Row>
|
||||||
<label for="email-input" class="col-md-4 col-form-label text-truncate text-md-right fw-bold">
|
<label for="email-input" class="col-md-4 col-form-label text-truncate text-md-right fw-bold">
|
||||||
{$t('email.address')}
|
{$t('email.address')}
|
||||||
|
|
|
@ -106,7 +106,34 @@
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
APILogout().then(
|
APILogout().then(
|
||||||
() => {
|
async () => {
|
||||||
|
if (window.happydomain_ory_kratos_url) {
|
||||||
|
await fetch(window.happydomain_ory_kratos_url + `self-service/logout/browser`,
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
headers: [
|
||||||
|
["Accept", "application/json"],
|
||||||
|
["Content-Type", "application/json"]
|
||||||
|
],
|
||||||
|
credentials: 'include',
|
||||||
|
}
|
||||||
|
).then(
|
||||||
|
async (data) => data.json()
|
||||||
|
).then(
|
||||||
|
async (state) => {
|
||||||
|
await fetch(state.logout_url,
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
headers: [
|
||||||
|
["Accept", "application/json"],
|
||||||
|
["Content-Type", "application/json"]
|
||||||
|
],
|
||||||
|
credentials: 'include',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
refreshUserSession().then(
|
refreshUserSession().then(
|
||||||
() => { },
|
() => { },
|
||||||
() => {
|
() => {
|
||||||
|
|
72
ui/src/lib/components/KratosFlow.svelte
Normal file
72
ui/src/lib/components/KratosFlow.svelte
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
<!--
|
||||||
|
This file is part of the happyDomain (R) project.
|
||||||
|
Copyright (c) 2022-2024 happyDomain
|
||||||
|
Authors: Pierre-Olivier Mercier, et al.
|
||||||
|
|
||||||
|
This program is offered under a commercial and under the AGPL license.
|
||||||
|
For commercial licensing, contact us at <contact@happydomain.org>.
|
||||||
|
|
||||||
|
For AGPL licensing:
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
|
import KratosNode from '$lib/components/KratosNode.svelte';
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
export let flow: String;
|
||||||
|
export let nodes: Array;
|
||||||
|
export let only: String;
|
||||||
|
export let submissionInProgress = false;
|
||||||
|
|
||||||
|
let values = { };
|
||||||
|
|
||||||
|
|
||||||
|
function initializeValues(nodes) {
|
||||||
|
const vls = { };
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (!only || node.group === only || node.group === 'default') {
|
||||||
|
vls[node.attributes.name] = node.attributes.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
values = vls;
|
||||||
|
}
|
||||||
|
$: initializeValues(nodes);
|
||||||
|
|
||||||
|
function submission() {
|
||||||
|
dispatch('submit', values);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form
|
||||||
|
class="container my-1"
|
||||||
|
method="post"
|
||||||
|
onsubmit="alert('test'); return false;"
|
||||||
|
on:submit|preventDefault={submission}
|
||||||
|
>
|
||||||
|
{#each nodes as node, i}
|
||||||
|
{#if !only || node.group === only || node.group === 'default'}
|
||||||
|
<KratosNode
|
||||||
|
{flow}
|
||||||
|
i={only + i}
|
||||||
|
{node}
|
||||||
|
{submissionInProgress}
|
||||||
|
bind:value={values[node.attributes.name]}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</form>
|
250
ui/src/lib/components/KratosForm.svelte
Normal file
250
ui/src/lib/components/KratosForm.svelte
Normal file
|
@ -0,0 +1,250 @@
|
||||||
|
<!--
|
||||||
|
This file is part of the happyDomain (R) project.
|
||||||
|
Copyright (c) 2022-2024 happyDomain
|
||||||
|
Authors: Pierre-Olivier Mercier, et al.
|
||||||
|
|
||||||
|
This program is offered under a commercial and under the AGPL license.
|
||||||
|
For commercial licensing, contact us at <contact@happydomain.org>.
|
||||||
|
|
||||||
|
For AGPL licensing:
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
|
import {
|
||||||
|
Alert,
|
||||||
|
Button,
|
||||||
|
Icon,
|
||||||
|
Spinner,
|
||||||
|
TabContent,
|
||||||
|
TabPane,
|
||||||
|
} from 'sveltestrap';
|
||||||
|
|
||||||
|
import KratosFlow from '$lib/components/KratosFlow.svelte';
|
||||||
|
import { t } from '$lib/translations';
|
||||||
|
import { toasts } from '$lib/stores/toasts';
|
||||||
|
import { refreshUserSession } from '$lib/stores/usersession';
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
export let flow: String;
|
||||||
|
export let tabs = false;
|
||||||
|
|
||||||
|
let form = { };
|
||||||
|
let groups = [];
|
||||||
|
let submissionInProgress = false;
|
||||||
|
let error = null;
|
||||||
|
let suggestLogout = false;
|
||||||
|
let action_method = "";
|
||||||
|
let action_url = "";
|
||||||
|
|
||||||
|
async function getFlow(aal) {
|
||||||
|
const res = await fetch(window.happydomain_ory_kratos_url + `self-service/${flow}/browser${aal ? '?aal=' + aal : ''}`,
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
headers: [
|
||||||
|
["Accept", "application/json"],
|
||||||
|
["Content-Type", "application/json"]
|
||||||
|
],
|
||||||
|
credentials: 'include',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
suggestLogout = false;
|
||||||
|
return {data: res, state: await res.json()};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function treatForm({ data, next, state }) {
|
||||||
|
if (next && data.status === 200) {
|
||||||
|
if (state.session && (!state.session.active || !state.session.identity)) {
|
||||||
|
state = (await getFlow('aal2')).state;
|
||||||
|
suggestLogout = true;
|
||||||
|
} else {
|
||||||
|
dispatch('success', state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.error && state.error.id === 'session_already_available') {
|
||||||
|
state = (await getFlow('aal2')).state;
|
||||||
|
suggestLogout = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.error) {
|
||||||
|
error = state.error;
|
||||||
|
toasts.addErrorToast({
|
||||||
|
title: state.error.message,
|
||||||
|
message: state.error.reason,
|
||||||
|
timeout: 30000,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
error = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.ui) {
|
||||||
|
const grps = [];
|
||||||
|
|
||||||
|
for(const node of state.ui.nodes) {
|
||||||
|
if (node.group !== "default" && !grps.includes(node.group)) {
|
||||||
|
grps.push(node.group);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form = state;
|
||||||
|
action_url = state.ui.action;
|
||||||
|
action_method = state.ui.method;
|
||||||
|
groups = grps;
|
||||||
|
}
|
||||||
|
|
||||||
|
submissionInProgress = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function submission(event) {
|
||||||
|
submissionInProgress = true;
|
||||||
|
fetch(action_url,
|
||||||
|
{
|
||||||
|
method: action_method,
|
||||||
|
body: JSON.stringify(event.detail),
|
||||||
|
headers: [
|
||||||
|
["Accept", "application/json"],
|
||||||
|
["Content-Type", "application/json"]
|
||||||
|
],
|
||||||
|
credentials: 'include',
|
||||||
|
}
|
||||||
|
).then(
|
||||||
|
async (data) => ({ data, next: true, state: await data.json() })
|
||||||
|
).then(treatForm);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function forceLogout() {
|
||||||
|
await fetch(window.happydomain_ory_kratos_url + `self-service/logout/browser`,
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
headers: [
|
||||||
|
["Accept", "application/json"],
|
||||||
|
["Content-Type", "application/json"]
|
||||||
|
],
|
||||||
|
credentials: 'include',
|
||||||
|
}
|
||||||
|
).then(
|
||||||
|
async (data) => data.json()
|
||||||
|
).then(
|
||||||
|
async (state) => {
|
||||||
|
await fetch(state.logout_url,
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
headers: [
|
||||||
|
["Accept", "application/json"],
|
||||||
|
["Content-Type", "application/json"]
|
||||||
|
],
|
||||||
|
credentials: 'include',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
formreq = getFlow();
|
||||||
|
formreq.then(treatForm);
|
||||||
|
}
|
||||||
|
|
||||||
|
let formreq = getFlow();
|
||||||
|
formreq.then(treatForm);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if error && error.message}
|
||||||
|
<Alert color="danger">
|
||||||
|
<Button
|
||||||
|
class="float-end"
|
||||||
|
color="link"
|
||||||
|
size="sm"
|
||||||
|
on:click={forceLogout}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name="door-open"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
<strong>
|
||||||
|
{error.message}.
|
||||||
|
</strong>
|
||||||
|
{error.reason}
|
||||||
|
{#if error.details}
|
||||||
|
<a href={error.details.docs} class="float-end" target="_blank">
|
||||||
|
<Icon
|
||||||
|
name="info-circle-fill"
|
||||||
|
title={error.details.hint}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
</Alert>
|
||||||
|
{/if}
|
||||||
|
{#if form && form.ui}
|
||||||
|
{#if form.ui.messages}
|
||||||
|
{#each form.ui.messages as message}
|
||||||
|
<Alert color={message.type === "error"?"danger":"info"}>
|
||||||
|
<strong>
|
||||||
|
{message.text}
|
||||||
|
</strong>
|
||||||
|
</Alert>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
{#if form.ui.nodes}
|
||||||
|
{#if tabs}
|
||||||
|
<TabContent>
|
||||||
|
{#each groups as group, ig}
|
||||||
|
<TabPane
|
||||||
|
class="pt-2"
|
||||||
|
tabId={group}
|
||||||
|
tab={group}
|
||||||
|
active={ig === 0}
|
||||||
|
>
|
||||||
|
<KratosFlow
|
||||||
|
{flow}
|
||||||
|
nodes={form.ui.nodes}
|
||||||
|
only={group}
|
||||||
|
{submissionInProgress}
|
||||||
|
on:submit={submission}
|
||||||
|
/>
|
||||||
|
</TabPane>
|
||||||
|
{/each}
|
||||||
|
</TabContent>
|
||||||
|
{:else}
|
||||||
|
{#each groups as group, i}
|
||||||
|
{#if i > 0}
|
||||||
|
<hr>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<KratosFlow
|
||||||
|
{flow}
|
||||||
|
nodes={form.ui.nodes}
|
||||||
|
only={group}
|
||||||
|
{submissionInProgress}
|
||||||
|
on:submit={submission}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
{#if suggestLogout}
|
||||||
|
<div class="d-flex align-items-center justify-content-center">
|
||||||
|
Quelque chose s'est mal passé ?
|
||||||
|
<Button
|
||||||
|
color="link"
|
||||||
|
type="button"
|
||||||
|
on:click={forceLogout}
|
||||||
|
>
|
||||||
|
Déconnectez-vous
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
160
ui/src/lib/components/KratosNode.svelte
Normal file
160
ui/src/lib/components/KratosNode.svelte
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
<!--
|
||||||
|
This file is part of the happyDomain (R) project.
|
||||||
|
Copyright (c) 2022-2024 happyDomain
|
||||||
|
Authors: Pierre-Olivier Mercier, et al.
|
||||||
|
|
||||||
|
This program is offered under a commercial and under the AGPL license.
|
||||||
|
For commercial licensing, contact us at <contact@happydomain.org>.
|
||||||
|
|
||||||
|
For AGPL licensing:
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FormGroup,
|
||||||
|
Input,
|
||||||
|
Label,
|
||||||
|
Spinner,
|
||||||
|
} from 'sveltestrap';
|
||||||
|
|
||||||
|
import { t } from '$lib/translations';
|
||||||
|
|
||||||
|
export let flow: String;
|
||||||
|
export let i;
|
||||||
|
export let node: Object;
|
||||||
|
export let submissionInProgress = false;
|
||||||
|
export let value: String;
|
||||||
|
|
||||||
|
let isSocial = false;
|
||||||
|
$: isSocial = node && node.attributes && (node.attributes.name === "provider" || node.attributes.name === "link") && node.group === "oidc"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if node.type === 'input'}
|
||||||
|
{#if node.attributes.type === "hidden"}
|
||||||
|
<input
|
||||||
|
{...node.attributes}
|
||||||
|
>
|
||||||
|
{:else if node.attributes.type === "submit"}
|
||||||
|
<FormGroup class="d-flex flex-column">
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
{...node.attributes}
|
||||||
|
disabled={submissionInProgress || node.attributes.disabled}
|
||||||
|
formnovalidate={isSocial || node.meta.label.id === 107008}
|
||||||
|
>
|
||||||
|
{#if submissionInProgress}
|
||||||
|
<Spinner size="sm" />
|
||||||
|
{/if}
|
||||||
|
{node.meta.label.text}
|
||||||
|
</Button>
|
||||||
|
</FormGroup>
|
||||||
|
{:else if node.attributes.type === "button"}
|
||||||
|
<FormGroup class="d-flex flex-column">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
color="secondary"
|
||||||
|
name={node.attributes.name}
|
||||||
|
value={node.attributes.value}
|
||||||
|
disabled={node.attributes.disabled || submissionInProgress}
|
||||||
|
on:click={(e) => {e.stopPropagation(); e.preventDefault(); const run = new Function(node.attributes.onclick); run();}}
|
||||||
|
>
|
||||||
|
{node.meta.label.text}
|
||||||
|
</Button>
|
||||||
|
</FormGroup>
|
||||||
|
{:else if node.attributes.type === "checkbox"}
|
||||||
|
<FormGroup>
|
||||||
|
<Input
|
||||||
|
type="checkbox"
|
||||||
|
label={node.meta.label.text}
|
||||||
|
id={"ns" + i}
|
||||||
|
{...node.attributes}
|
||||||
|
disabled={submissionInProgress || node.attributes.disabled}
|
||||||
|
invalid={node.messages.find(({ type }) => type === "error")}
|
||||||
|
feedback={node.messages.map(({ text, id }, k) => text)}
|
||||||
|
on:changed={(e) => { if (e.target.checked) { value = node.attributes.value; } else { value = undefined; } }}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
{:else}
|
||||||
|
<FormGroup>
|
||||||
|
<Label for={"ns" + i}>
|
||||||
|
{#if node.meta.label.id == 107001}
|
||||||
|
{$t('common.password')}
|
||||||
|
{:else if node.meta.label.id == 107002 || node.meta.label.id == 107004}
|
||||||
|
{$t('email.address')}
|
||||||
|
{:else}
|
||||||
|
{node.meta.label.text}
|
||||||
|
{/if}
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id={"ns" + i}
|
||||||
|
{...node.attributes}
|
||||||
|
disabled={submissionInProgress || node.attributes.disabled}
|
||||||
|
invalid={node.messages.find(({ type }) => type === "error")}
|
||||||
|
feedback={node.messages.map(({ text, id }, k) => text)}
|
||||||
|
placeholder={node.attributes.placeholder?node.attributes.placeholder:(node.meta.label.id===107001?"pMockapetris@usc.edu":(node.meta.label.id===107002?"xXxXxXxXxX":""))}
|
||||||
|
bind:value={value}
|
||||||
|
/>
|
||||||
|
{#if flow === "login" && node.attributes.type === "password"}
|
||||||
|
<div class="form-text">
|
||||||
|
<a
|
||||||
|
href="/forgotten-password"
|
||||||
|
>
|
||||||
|
{$t('password.forgotten')}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</FormGroup>
|
||||||
|
{/if}
|
||||||
|
{:else if node.type === 'a'}
|
||||||
|
<Button
|
||||||
|
href={node.attributes.href}
|
||||||
|
>
|
||||||
|
{node.attributes.title.text}
|
||||||
|
</Button>
|
||||||
|
{:else if node.type === 'img'}
|
||||||
|
<img
|
||||||
|
src={node.attributes.src}
|
||||||
|
alt={node.meta.label?.text}
|
||||||
|
/>
|
||||||
|
{:else if node.type === 'script'}
|
||||||
|
<script {...node.attributes}></script>
|
||||||
|
{:else if node.type === 'text'}
|
||||||
|
<p>
|
||||||
|
{node.meta?.label?.text}
|
||||||
|
</p>
|
||||||
|
{#if node.attributes.text.id === 1050015}
|
||||||
|
<div
|
||||||
|
class="container-fluid"
|
||||||
|
>
|
||||||
|
<div class="row">
|
||||||
|
{#each node.attributes.text.context.secrets as text, k}
|
||||||
|
<div
|
||||||
|
key={k}
|
||||||
|
class="col-xs-3"
|
||||||
|
>
|
||||||
|
<code>{text.id === 1050014 ? "Used" : text.text}</code>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div>
|
||||||
|
<pre>
|
||||||
|
{node.attributes.text.text}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
|
@ -22,7 +22,7 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
@ -33,11 +33,11 @@
|
||||||
} from 'sveltestrap';
|
} from 'sveltestrap';
|
||||||
|
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { authUser, cleanUserSession } from '$lib/api/user';
|
import { authUser } from '$lib/api/user';
|
||||||
import type { LoginForm } from '$lib/model/user';
|
import type { LoginForm } from '$lib/model/user';
|
||||||
import { providers } from '$lib/stores/providers';
|
|
||||||
import { toasts } from '$lib/stores/toasts';
|
import { toasts } from '$lib/stores/toasts';
|
||||||
import { refreshUserSession } from '$lib/stores/usersession';
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
let loginForm: LoginForm = {
|
let loginForm: LoginForm = {
|
||||||
email: "",
|
email: "",
|
||||||
|
@ -60,13 +60,10 @@
|
||||||
authUser(loginForm)
|
authUser(loginForm)
|
||||||
.then(
|
.then(
|
||||||
() => {
|
() => {
|
||||||
cleanUserSession();
|
|
||||||
providers.set(null);
|
|
||||||
formSent = false;
|
formSent = false;
|
||||||
emailState = true;
|
emailState = true;
|
||||||
passwordState = true;
|
passwordState = true;
|
||||||
refreshUserSession();
|
dispatch('success');
|
||||||
goto('/');
|
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
formSent = false;
|
formSent = false;
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
@ -39,6 +39,7 @@
|
||||||
import { checkWeakPassword, checkPasswordConfirmation } from '$lib/password';
|
import { checkWeakPassword, checkPasswordConfirmation } from '$lib/password';
|
||||||
import { toasts } from '$lib/stores/toasts';
|
import { toasts } from '$lib/stores/toasts';
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
let signupForm: SignUpForm = {
|
let signupForm: SignUpForm = {
|
||||||
email: "",
|
email: "",
|
||||||
|
@ -70,13 +71,7 @@
|
||||||
.then(
|
.then(
|
||||||
() => {
|
() => {
|
||||||
formSent = false;
|
formSent = false;
|
||||||
toasts.addToast({
|
dispatch('success');
|
||||||
title: $t('account.signup.success'),
|
|
||||||
message: $t('email.instruction.check-inbox'),
|
|
||||||
type: 'success',
|
|
||||||
timeout: 5000,
|
|
||||||
});
|
|
||||||
goto('/login');
|
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
formSent = false;
|
formSent = false;
|
||||||
|
|
|
@ -332,6 +332,7 @@
|
||||||
},
|
},
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
"save": "Save settings",
|
"save": "Save settings",
|
||||||
|
"security": "Account Security",
|
||||||
"showrrtypes": "Show resource type associated with services (for users familiar with DNS)",
|
"showrrtypes": "Show resource type associated with services (for users familiar with DNS)",
|
||||||
"success": "Continue to enjoy happyDomain.",
|
"success": "Continue to enjoy happyDomain.",
|
||||||
"success-change": "Your settings has been saved.",
|
"success-change": "Your settings has been saved.",
|
||||||
|
|
|
@ -305,6 +305,7 @@
|
||||||
},
|
},
|
||||||
"language": "Langue",
|
"language": "Langue",
|
||||||
"save": "Enregistrer les paramètres",
|
"save": "Enregistrer les paramètres",
|
||||||
|
"security": "Sécurité du compte",
|
||||||
"showrrtypes": "Afficher les types d'enregistrement associés aux services (pour les utilisateurs familiers de la terminologie DNS)",
|
"showrrtypes": "Afficher les types d'enregistrement associés aux services (pour les utilisateurs familiers de la terminologie DNS)",
|
||||||
"success": "Vous pouvez continuer d'apprécier happyDomain.",
|
"success": "Vous pouvez continuer d'apprécier happyDomain.",
|
||||||
"success-change": "Vos paramètres ont été sauvegardés.",
|
"success-change": "Vos paramètres ont été sauvegardés.",
|
||||||
|
|
|
@ -28,7 +28,9 @@
|
||||||
} from 'sveltestrap';
|
} from 'sveltestrap';
|
||||||
|
|
||||||
import ForgottenPasswordForm from '$lib/components/ForgottenPasswordForm.svelte';
|
import ForgottenPasswordForm from '$lib/components/ForgottenPasswordForm.svelte';
|
||||||
|
import KratosForm from '$lib/components/KratosForm.svelte';
|
||||||
import RecoverAccountForm from '$lib/components/RecoverAccountForm.svelte';
|
import RecoverAccountForm from '$lib/components/RecoverAccountForm.svelte';
|
||||||
|
import { t } from '$lib/translations';
|
||||||
|
|
||||||
let error = "";
|
let error = "";
|
||||||
export let data;
|
export let data;
|
||||||
|
@ -41,7 +43,14 @@
|
||||||
</Alert>
|
</Alert>
|
||||||
{:else if data.user && data.key}
|
{:else if data.user && data.key}
|
||||||
<RecoverAccountForm user={data.user} key={data.key} />
|
<RecoverAccountForm user={data.user} key={data.key} />
|
||||||
|
{:else}
|
||||||
|
<p class="text-center">
|
||||||
|
{$t('email.recover')}.
|
||||||
|
</p>
|
||||||
|
{#if window.happydomain_ory_kratos_url}
|
||||||
|
<KratosForm flow="recovery" />
|
||||||
{:else}
|
{:else}
|
||||||
<ForgottenPasswordForm />
|
<ForgottenPasswordForm />
|
||||||
{/if}
|
{/if}
|
||||||
|
{/if}
|
||||||
</Container>
|
</Container>
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardBody,
|
CardBody,
|
||||||
|
@ -31,8 +33,20 @@
|
||||||
Row,
|
Row,
|
||||||
} from 'sveltestrap';
|
} from 'sveltestrap';
|
||||||
|
|
||||||
import { t } from '$lib/translations';
|
|
||||||
import SignUpForm from '$lib/components/SignUpForm.svelte';
|
import SignUpForm from '$lib/components/SignUpForm.svelte';
|
||||||
|
import KratosForm from '$lib/components/KratosForm.svelte';
|
||||||
|
import { toasts } from '$lib/stores/toasts';
|
||||||
|
import { t } from '$lib/translations';
|
||||||
|
|
||||||
|
function next() {
|
||||||
|
toasts.addToast({
|
||||||
|
title: $t('account.signup.success'),
|
||||||
|
message: $t('email.instruction.check-inbox'),
|
||||||
|
type: 'success',
|
||||||
|
timeout: 5000,
|
||||||
|
});
|
||||||
|
goto('/login');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Container class="my-3">
|
<Container class="my-3">
|
||||||
|
@ -48,7 +62,16 @@
|
||||||
</h6>
|
</h6>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<SignUpForm />
|
{#if window.happydomain_ory_kratos_url}
|
||||||
|
<KratosForm
|
||||||
|
flow="registration"
|
||||||
|
on:success={next}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<SignUpForm
|
||||||
|
on:success={next}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
<div class="mt-3 text-justify">
|
<div class="mt-3 text-justify">
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardBody,
|
CardBody,
|
||||||
|
@ -32,7 +34,18 @@
|
||||||
} from 'sveltestrap';
|
} from 'sveltestrap';
|
||||||
|
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
|
import { cleanUserSession } from '$lib/api/user';
|
||||||
import LoginForm from '$lib/components/LoginForm.svelte';
|
import LoginForm from '$lib/components/LoginForm.svelte';
|
||||||
|
import KratosForm from '$lib/components/KratosForm.svelte';
|
||||||
|
import { providers } from '$lib/stores/providers';
|
||||||
|
import { refreshUserSession } from '$lib/stores/usersession';
|
||||||
|
|
||||||
|
function next() {
|
||||||
|
cleanUserSession();
|
||||||
|
providers.set(null);
|
||||||
|
refreshUserSession();
|
||||||
|
goto('/');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Container class="my-3">
|
<Container class="my-3">
|
||||||
|
@ -48,7 +61,16 @@
|
||||||
</h6>
|
</h6>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<LoginForm />
|
{#if window.happydomain_ory_kratos_url}
|
||||||
|
<KratosForm
|
||||||
|
flow="login"
|
||||||
|
on:success={next}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<LoginForm
|
||||||
|
on:success={next}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
<div class="text-center mt-4">
|
<div class="text-center mt-4">
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
|
|
||||||
import ChangePasswordForm from '$lib/components/ChangePasswordForm.svelte';
|
import ChangePasswordForm from '$lib/components/ChangePasswordForm.svelte';
|
||||||
import DeleteAccountCard from '$lib/components/DeleteAccountCard.svelte';
|
import DeleteAccountCard from '$lib/components/DeleteAccountCard.svelte';
|
||||||
|
import KratosForm from '$lib/components/KratosForm.svelte';
|
||||||
import UserSettingsForm from '$lib/components/UserSettingsForm.svelte';
|
import UserSettingsForm from '$lib/components/UserSettingsForm.svelte';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
import { userSession } from '$lib/stores/usersession';
|
import { userSession } from '$lib/stores/usersession';
|
||||||
|
@ -57,14 +58,21 @@
|
||||||
{/if}
|
{/if}
|
||||||
</Row>
|
</Row>
|
||||||
{#if $userSession.email !== '_no_auth'}
|
{#if $userSession.email !== '_no_auth'}
|
||||||
<h2 id="password-change">
|
<h2 id="security-settings">
|
||||||
{$t('password.change')}
|
{$t('settings.security')}
|
||||||
</h2>
|
</h2>
|
||||||
<Row>
|
<Row>
|
||||||
<Col md={{size: 8, offset: 2}}>
|
<Col md={{size: 8, offset: 2}}>
|
||||||
<Card>
|
<Card>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
|
{#if window.happydomain_ory_kratos_url}
|
||||||
|
<KratosForm
|
||||||
|
flow="settings"
|
||||||
|
tabs
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
<ChangePasswordForm />
|
<ChangePasswordForm />
|
||||||
|
{/if}
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user