mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 12:07:37 +00:00
feat: actions (#2377)
* feat(actions): begin api * feat(actions): begin api * api and projections * fix: handle multiple statements for a single event in projections * export func type * fix test * update to new reduce interface * flows in login * feat: jwt idp * feat: command side * feat: add tests * actions and flows * fill idp views with jwt idps and return apis * add jwtEndpoint to jwt idp * begin jwt request handling * add feature * merge * merge * handle jwt idp * cleanup * bug fixes * autoregister * get token from specific header name * fix: proto * fixes * i18n * begin tests * fix and log http proxy * remove docker cache * fixes * usergrants in actions api * tests adn cleanup * cleanup * fix add user grant * set login context * i18n Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>
This commit is contained in:
112
internal/actions/actions.go
Normal file
112
internal/actions/actions.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/dop251/goja"
|
||||
"github.com/dop251/goja_nodejs/console"
|
||||
"github.com/dop251/goja_nodejs/require"
|
||||
)
|
||||
|
||||
var ErrHalt = errors.New("interrupt")
|
||||
|
||||
type jsAction func(*Context, *API) error
|
||||
|
||||
func Run(ctx *Context, api *API, script, name string, timeout time.Duration, allowedToFail bool) error {
|
||||
if timeout <= 0 || timeout > 20 {
|
||||
timeout = 20 * time.Second
|
||||
}
|
||||
prepareTimeout := timeout
|
||||
if prepareTimeout > 5 {
|
||||
prepareTimeout = 5 * time.Second
|
||||
}
|
||||
vm, err := prepareRun(script, prepareTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var fn jsAction
|
||||
jsFn := vm.Get(name)
|
||||
if jsFn == nil {
|
||||
return errors.New("function not found")
|
||||
}
|
||||
err = vm.ExportTo(jsFn, &fn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t := setInterrupt(vm, timeout)
|
||||
defer func() {
|
||||
t.Stop()
|
||||
}()
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil && !allowedToFail {
|
||||
err, ok := r.(error)
|
||||
if !ok {
|
||||
e, ok := r.(string)
|
||||
if ok {
|
||||
err = errors.New(e)
|
||||
}
|
||||
}
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
}()
|
||||
err = fn(ctx, api)
|
||||
if err != nil && !allowedToFail {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
errCh <- nil
|
||||
}()
|
||||
return <-errCh
|
||||
}
|
||||
|
||||
func newRuntime() *goja.Runtime {
|
||||
vm := goja.New()
|
||||
|
||||
printer := console.PrinterFunc(func(s string) {
|
||||
logging.Log("ACTIONS-dfgg2").Debug(s)
|
||||
})
|
||||
registry := new(require.Registry)
|
||||
registry.Enable(vm)
|
||||
registry.RegisterNativeModule("console", console.RequireWithPrinter(printer))
|
||||
console.Enable(vm)
|
||||
|
||||
return vm
|
||||
}
|
||||
|
||||
func prepareRun(script string, timeout time.Duration) (*goja.Runtime, error) {
|
||||
vm := newRuntime()
|
||||
t := setInterrupt(vm, timeout)
|
||||
defer func() {
|
||||
t.Stop()
|
||||
}()
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r != nil {
|
||||
errCh <- r.(error)
|
||||
return
|
||||
}
|
||||
}()
|
||||
_, err := vm.RunString(script)
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
errCh <- nil
|
||||
}()
|
||||
return vm, <-errCh
|
||||
}
|
||||
|
||||
func setInterrupt(vm *goja.Runtime, timeout time.Duration) *time.Timer {
|
||||
vm.ClearInterrupt()
|
||||
return time.AfterFunc(timeout, func() {
|
||||
vm.Interrupt(ErrHalt)
|
||||
})
|
||||
}
|
105
internal/actions/api.go
Normal file
105
internal/actions/api.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type API map[string]interface{}
|
||||
|
||||
func (a API) set(name string, value interface{}) {
|
||||
map[string]interface{}(a)[name] = value
|
||||
}
|
||||
|
||||
func (a *API) SetHuman(human *domain.Human) *API {
|
||||
a.set("setFirstName", func(firstName string) {
|
||||
human.FirstName = firstName
|
||||
})
|
||||
a.set("setLastName", func(lastName string) {
|
||||
human.LastName = lastName
|
||||
})
|
||||
a.set("setNickName", func(nickName string) {
|
||||
human.NickName = nickName
|
||||
})
|
||||
a.set("setDisplayName", func(displayName string) {
|
||||
human.DisplayName = displayName
|
||||
})
|
||||
a.set("setPreferredLanguage", func(preferredLanguage string) {
|
||||
human.PreferredLanguage = language.Make(preferredLanguage)
|
||||
})
|
||||
a.set("setGender", func(gender domain.Gender) {
|
||||
human.Gender = gender
|
||||
})
|
||||
a.set("setUsername", func(username string) {
|
||||
human.Username = username
|
||||
})
|
||||
a.set("setEmail", func(email string) {
|
||||
if human.Email == nil {
|
||||
human.Email = &domain.Email{}
|
||||
}
|
||||
human.Email.EmailAddress = email
|
||||
})
|
||||
a.set("setEmailVerified", func(verified bool) {
|
||||
if human.Email == nil {
|
||||
return
|
||||
}
|
||||
human.Email.IsEmailVerified = verified
|
||||
})
|
||||
a.set("setPhone", func(email string) {
|
||||
if human.Phone == nil {
|
||||
human.Phone = &domain.Phone{}
|
||||
}
|
||||
human.Phone.PhoneNumber = email
|
||||
})
|
||||
a.set("setPhoneVerified", func(verified bool) {
|
||||
if human.Phone == nil {
|
||||
return
|
||||
}
|
||||
human.Phone.IsPhoneVerified = verified
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *API) SetExternalUser(user *domain.ExternalUser) *API {
|
||||
a.set("setFirstName", func(firstName string) {
|
||||
user.FirstName = firstName
|
||||
})
|
||||
a.set("setLastName", func(lastName string) {
|
||||
user.LastName = lastName
|
||||
})
|
||||
a.set("setNickName", func(nickName string) {
|
||||
user.NickName = nickName
|
||||
})
|
||||
a.set("setDisplayName", func(displayName string) {
|
||||
user.DisplayName = displayName
|
||||
})
|
||||
a.set("setPreferredLanguage", func(preferredLanguage string) {
|
||||
user.PreferredLanguage = language.Make(preferredLanguage)
|
||||
})
|
||||
a.set("setPreferredUsername", func(username string) {
|
||||
user.PreferredUsername = username
|
||||
})
|
||||
a.set("setEmail", func(email string) {
|
||||
user.Email = email
|
||||
})
|
||||
a.set("setEmailVerified", func(verified bool) {
|
||||
user.IsEmailVerified = verified
|
||||
})
|
||||
a.set("setPhone", func(phone string) {
|
||||
user.Phone = phone
|
||||
})
|
||||
a.set("setPhoneVerified", func(verified bool) {
|
||||
user.IsPhoneVerified = verified
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *API) SetMetadata(metadata *[]*domain.Metadata) *API {
|
||||
a.set("metadata", metadata)
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *API) SetUserGrants(usergrants *[]UserGrant) *API {
|
||||
a.set("userGrants", usergrants)
|
||||
return a
|
||||
}
|
33
internal/actions/context.go
Normal file
33
internal/actions/context.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
)
|
||||
|
||||
type Context map[string]interface{}
|
||||
|
||||
func (c Context) set(name string, value interface{}) {
|
||||
map[string]interface{}(c)[name] = value
|
||||
}
|
||||
|
||||
func (c *Context) SetToken(t *oidc.Tokens) *Context {
|
||||
if t.Token != nil && t.Token.AccessToken != "" {
|
||||
c.set("accessToken", t.AccessToken)
|
||||
}
|
||||
if t.IDToken != "" {
|
||||
c.set("idToken", t.IDToken)
|
||||
}
|
||||
if t.IDTokenClaims != nil {
|
||||
c.set("getClaim", func(claim string) interface{} { return t.IDTokenClaims.GetClaim(claim) })
|
||||
c.set("claimsJSON", func() (string, error) {
|
||||
c, err := json.Marshal(t.IDTokenClaims)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(c), nil
|
||||
})
|
||||
}
|
||||
return c
|
||||
}
|
55
internal/actions/provided.go
Normal file
55
internal/actions/provided.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
type UserGrant struct {
|
||||
ProjectID string
|
||||
ProjectGrantID string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
func appendUserGrant(list *[]UserGrant) func(goja.FunctionCall) goja.Value {
|
||||
return func(call goja.FunctionCall) goja.Value {
|
||||
userGrantMap := call.Argument(0).Export()
|
||||
userGrant, _ := userGrantFromMap(userGrantMap)
|
||||
*list = append(*list, userGrant)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func userGrantFromMap(grantMap interface{}) (UserGrant, error) {
|
||||
m, ok := grantMap.(map[string]interface{})
|
||||
if !ok {
|
||||
return UserGrant{}, errors.New("invalid")
|
||||
}
|
||||
projectID, ok := m["projectID"].(string)
|
||||
if !ok {
|
||||
return UserGrant{}, errors.New("invalid")
|
||||
}
|
||||
var projectGrantID string
|
||||
if id, ok := m["projectGrantID"]; ok {
|
||||
projectGrantID, ok = id.(string)
|
||||
if !ok {
|
||||
return UserGrant{}, errors.New("invalid")
|
||||
}
|
||||
}
|
||||
var roles []string
|
||||
if r := m["roles"]; r != nil {
|
||||
rs, ok := r.([]interface{})
|
||||
if !ok {
|
||||
return UserGrant{}, errors.New("invalid")
|
||||
}
|
||||
for _, role := range rs {
|
||||
roles = append(roles, role.(string))
|
||||
}
|
||||
}
|
||||
return UserGrant{
|
||||
ProjectID: projectID,
|
||||
ProjectGrantID: projectGrantID,
|
||||
Roles: roles,
|
||||
}, nil
|
||||
}
|
Reference in New Issue
Block a user