mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 14:47:33 +00:00
feat(actions): local users (#5089)
Actions are extended to to local users. It's possible to run custom code during registration and authentication of local users.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -19,6 +19,7 @@ profile.cov
|
|||||||
__debug_bin
|
__debug_bin
|
||||||
debug
|
debug
|
||||||
sandbox.go
|
sandbox.go
|
||||||
|
/cmd/dev/
|
||||||
|
|
||||||
# IDE
|
# IDE
|
||||||
.idea
|
.idea
|
||||||
|
@@ -259,14 +259,14 @@ npm run lint:fix
|
|||||||
# Install npm dependencies
|
# Install npm dependencies
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
# Run all tests in a headless browser
|
# Run all e2e tests
|
||||||
npm run e2e:dev
|
npm run e2e:dev -- --headed
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also open the test suite interactively for fast success feedback on specific tests.
|
You can also open the test suite interactively for fast success feedback on specific tests.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run all tests in a headless browser
|
# Run tests interactively
|
||||||
npm run open:dev
|
npm run open:dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@@ -202,7 +202,6 @@ export class AppComponent implements OnDestroy {
|
|||||||
.getActiveOrg()
|
.getActiveOrg()
|
||||||
.then(async (org) => {
|
.then(async (org) => {
|
||||||
this.org = org;
|
this.org = org;
|
||||||
|
|
||||||
// TODO add when console storage is implemented
|
// TODO add when console storage is implemented
|
||||||
// this.startIntroWorkflow();
|
// this.startIntroWorkflow();
|
||||||
})
|
})
|
||||||
|
@@ -6,7 +6,13 @@
|
|||||||
<p class="desc cnsl-secondary-text">{{ 'USER.MFA.DIALOG.ADD_MFA_DESCRIPTION' | translate }}</p>
|
<p class="desc cnsl-secondary-text">{{ 'USER.MFA.DIALOG.ADD_MFA_DESCRIPTION' | translate }}</p>
|
||||||
|
|
||||||
<div class="type-selection">
|
<div class="type-selection">
|
||||||
<button mat-raised-button color="primary" [disabled]="data.otpDisabled" (click)="selectType(AuthFactorType.OTP)">
|
<button
|
||||||
|
mat-raised-button
|
||||||
|
color="primary"
|
||||||
|
[disabled]="data.otpDisabled$ | async"
|
||||||
|
(click)="selectType(AuthFactorType.OTP)"
|
||||||
|
data-e2e="add-factor-otp"
|
||||||
|
>
|
||||||
<div class="otp-btn">
|
<div class="otp-btn">
|
||||||
<mat-icon class="icon" svgIcon="mdi_radar"></mat-icon>
|
<mat-icon class="icon" svgIcon="mdi_radar"></mat-icon>
|
||||||
<span>{{ 'USER.MFA.OTP' | translate }}</span>
|
<span>{{ 'USER.MFA.OTP' | translate }}</span>
|
||||||
|
@@ -37,7 +37,7 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
|||||||
public AuthFactorState: any = AuthFactorState;
|
public AuthFactorState: any = AuthFactorState;
|
||||||
|
|
||||||
public error: string = '';
|
public error: string = '';
|
||||||
public otpAvailable: boolean = false;
|
public otpDisabled$ = new BehaviorSubject<boolean>(true);
|
||||||
|
|
||||||
constructor(private service: GrpcAuthService, private toast: ToastService, private dialog: MatDialog) {}
|
constructor(private service: GrpcAuthService, private toast: ToastService, private dialog: MatDialog) {}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
|||||||
public addAuthFactor(): void {
|
public addAuthFactor(): void {
|
||||||
const dialogRef = this.dialog.open(AuthFactorDialogComponent, {
|
const dialogRef = this.dialog.open(AuthFactorDialogComponent, {
|
||||||
data: {
|
data: {
|
||||||
otpDisabled: !this.otpAvailable,
|
otpDisabled$: this.otpDisabled$,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
const index = list.findIndex((mfa) => mfa.otp);
|
const index = list.findIndex((mfa) => mfa.otp);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
this.otpAvailable = true;
|
this.otpDisabled$.next(false);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
@@ -1,16 +1,14 @@
|
|||||||
---
|
---
|
||||||
title: Login flows
|
title: Complement Token Flow
|
||||||
---
|
---
|
||||||
|
|
||||||
## Complement Token
|
|
||||||
|
|
||||||
This flow is executed during the creation of tokens and token introspection.
|
This flow is executed during the creation of tokens and token introspection.
|
||||||
|
|
||||||
### Pre Userinfo creation
|
## Pre Userinfo creation
|
||||||
|
|
||||||
This trigger is called before userinfo are set in the token or response.
|
This trigger is called before userinfo are set in the token or response.
|
||||||
|
|
||||||
#### Parameters of Pre Userinfo creation
|
### Parameters of Pre Userinfo creation
|
||||||
|
|
||||||
- `ctx`
|
- `ctx`
|
||||||
The first parameter contains the following fields:
|
The first parameter contains the following fields:
|
||||||
@@ -27,11 +25,11 @@ This trigger is called before userinfo are set in the token or response.
|
|||||||
- `setMetadata(string, Any)`
|
- `setMetadata(string, Any)`
|
||||||
Key of the metadata and any value
|
Key of the metadata and any value
|
||||||
|
|
||||||
### Pre access token creation
|
## Pre access token creation
|
||||||
|
|
||||||
This trigger is called before the claims are set in the access token and the token type is `jwt`.
|
This trigger is called before the claims are set in the access token and the token type is `jwt`.
|
||||||
|
|
||||||
#### Parameters of Pre access token creation
|
### Parameters of Pre access token creation
|
||||||
|
|
||||||
- `ctx`
|
- `ctx`
|
||||||
The first parameter contains the following fields:
|
The first parameter contains the following fields:
|
@@ -1,16 +1,14 @@
|
|||||||
---
|
---
|
||||||
title: Register flows
|
title: External Authentication Flow
|
||||||
---
|
---
|
||||||
|
|
||||||
## External Authentication
|
|
||||||
|
|
||||||
This flow is executed if the user logs in using an [identity provider](../../guides/integrate/identity-brokering) or using a [jwt token](../../concepts/structure/jwt_idp).
|
This flow is executed if the user logs in using an [identity provider](../../guides/integrate/identity-brokering) or using a [jwt token](../../concepts/structure/jwt_idp).
|
||||||
|
|
||||||
### Post Authentication
|
## Post Authentication
|
||||||
|
|
||||||
A user has authenticated externally. ZITADEL retrieved and mapped the external information.
|
A user has authenticated externally. ZITADEL retrieved and mapped the external information.
|
||||||
|
|
||||||
#### Parameters of post authentication action
|
### Parameters of Post Authentication Action
|
||||||
|
|
||||||
- `ctx`
|
- `ctx`
|
||||||
The first parameter contains the following fields
|
The first parameter contains the following fields
|
||||||
@@ -24,6 +22,7 @@ The first parameter contains the following fields
|
|||||||
The id token which will be returned to the user
|
The id token which will be returned to the user
|
||||||
- `v1`
|
- `v1`
|
||||||
- `externalUser()` [*externalUser*](./objects#external-user)
|
- `externalUser()` [*externalUser*](./objects#external-user)
|
||||||
|
- `authRequest` [*auth request*](/docs/apis/actions/objects#auth-request)
|
||||||
- `api`
|
- `api`
|
||||||
The second parameter contains the following fields
|
The second parameter contains the following fields
|
||||||
- `v1`
|
- `v1`
|
||||||
@@ -53,16 +52,17 @@ The first parameter contains the following fields
|
|||||||
- `metadata`
|
- `metadata`
|
||||||
Array of [*metadata*](./objects#metadata-with-value-as-bytes). This function is deprecated, please use `api.v1.user.appendMetadata`
|
Array of [*metadata*](./objects#metadata-with-value-as-bytes). This function is deprecated, please use `api.v1.user.appendMetadata`
|
||||||
|
|
||||||
### Pre Creation
|
## Pre Creation
|
||||||
|
|
||||||
A user selected **Register** on the overview page after external authentication. ZITADEL did not create the user yet.
|
A user selected **Register** on the overview page after external authentication. ZITADEL did not create the user yet.
|
||||||
|
|
||||||
#### Parameters of Pre Creation
|
### Parameters of Pre Creation
|
||||||
|
|
||||||
- `ctx`
|
- `ctx`
|
||||||
The first parameter contains the following fields
|
The first parameter contains the following fields
|
||||||
- `v1`
|
- `v1`
|
||||||
- `user` [*human*](./objects#human-user)
|
- `user` [*human*](./objects#human-user)
|
||||||
|
- `authRequest` [*auth request*](/docs/apis/actions/objects#auth-request)
|
||||||
- `api`
|
- `api`
|
||||||
The second parameter contains the following fields
|
The second parameter contains the following fields
|
||||||
- `metadata`
|
- `metadata`
|
||||||
@@ -95,16 +95,17 @@ A user selected **Register** on the overview page after external authentication.
|
|||||||
- `appendMetadata(string, Any)`
|
- `appendMetadata(string, Any)`
|
||||||
The first parameter represents the key and the second a value which will be stored
|
The first parameter represents the key and the second a value which will be stored
|
||||||
|
|
||||||
### Post Creation
|
## Post Creation
|
||||||
|
|
||||||
A user selected **Register** on the overview page after external authentication and ZITADEL successfully created the user.
|
A user selected **Register** on the overview page after external authentication and ZITADEL successfully created the user.
|
||||||
|
|
||||||
#### Parameters of Post Creation
|
### Parameters of Post Creation
|
||||||
|
|
||||||
- `ctx`
|
- `ctx`
|
||||||
The first parameter contains the following fields
|
The first parameter contains the following fields
|
||||||
- `v1`
|
- `v1`
|
||||||
- `getUser()` [*user*](./objects#user)
|
- `getUser()` [*user*](./objects#user)
|
||||||
|
- `authRequest` [*auth request*](/docs/apis/actions/objects#auth-request)
|
||||||
- `api`
|
- `api`
|
||||||
The second parameter contains the following fields
|
The second parameter contains the following fields
|
||||||
- `userGrants` Array of [*userGrant*](./objects#user-grant)'s
|
- `userGrants` Array of [*userGrant*](./objects#user-grant)'s
|
90
docs/docs/apis/actions/internal-authentication.md
Normal file
90
docs/docs/apis/actions/internal-authentication.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
---
|
||||||
|
title: Internal Authentication Flow
|
||||||
|
---
|
||||||
|
|
||||||
|
## Post Authentication
|
||||||
|
|
||||||
|
A user has authenticated directly at ZITADEL.
|
||||||
|
ZITADEL validated the users inputs for password, one-time password, security key or passwordless factor.
|
||||||
|
Each validation step triggers the action.
|
||||||
|
|
||||||
|
### Parameters of Post Authentication Action
|
||||||
|
|
||||||
|
- `ctx`
|
||||||
|
The first parameter contains the following fields
|
||||||
|
- `v1`
|
||||||
|
- `authMethod` *string*
|
||||||
|
This is one of "password", "OTP", "U2F" or "passwordless"
|
||||||
|
- `authError` *string*
|
||||||
|
This is a verification errors string representation. If the verification succeeds, this is "none"
|
||||||
|
- `authRequest` [*auth request*](/docs/apis/actions/objects#auth-request)
|
||||||
|
- `api`
|
||||||
|
The second parameter contains the following fields
|
||||||
|
- `metadata`
|
||||||
|
Array of [*metadata*](./objects#metadata-with-value-as-bytes). This function is deprecated, please use `api.v1.user.appendMetadata`
|
||||||
|
- `v1`
|
||||||
|
- `user`
|
||||||
|
- `appendMetadata(string, Any)`
|
||||||
|
The first parameter represents the key and the second a value which will be stored
|
||||||
|
|
||||||
|
## Pre Creation
|
||||||
|
|
||||||
|
A user registers directly at ZITADEL.
|
||||||
|
ZITADEL did not create the user yet.
|
||||||
|
|
||||||
|
### Parameters of Pre Creation
|
||||||
|
|
||||||
|
- `ctx`
|
||||||
|
The first parameter contains the following fields
|
||||||
|
- `v1`
|
||||||
|
- `user` [*human*](./objects#human-user)
|
||||||
|
- `authRequest` [*auth request*](/docs/apis/actions/objects#auth-request)
|
||||||
|
- `api`
|
||||||
|
The second parameter contains the following fields
|
||||||
|
- `metadata`
|
||||||
|
Array of [*metadata*](./objects#metadata-with-value-as-bytes). This function is deprecated, please use `api.v1.user.appendMetadata`
|
||||||
|
- `setFirstName(string)`
|
||||||
|
Sets the first name
|
||||||
|
- `setLastName(string)`
|
||||||
|
Sets the last name
|
||||||
|
- `setNickName(string)`
|
||||||
|
Sets the nick name
|
||||||
|
- `setDisplayName(string)`
|
||||||
|
Sets the display name
|
||||||
|
- `setPreferredLanguage(string)`
|
||||||
|
Sets the preferred language, the string has to be a valid language tag as defined in [RFC 5646](https://www.rfc-editor.org/rfc/rfc5646)
|
||||||
|
- `setGender(int)`
|
||||||
|
Sets the gender.
|
||||||
|
<ul><li>0: unspecified</li><li>1: female</li><li>2: male</li><li>3: diverse</li></ul>
|
||||||
|
- `setUsername(string)`
|
||||||
|
Sets the username
|
||||||
|
- `setEmail(string)`
|
||||||
|
Sets the email
|
||||||
|
- `setEmailVerified(bool)`
|
||||||
|
If true the email set is verified without user interaction
|
||||||
|
- `setPhone(string)`
|
||||||
|
Sets the phone number
|
||||||
|
- `setPhoneVerified(bool)`
|
||||||
|
If true the phone number set is verified without user interaction
|
||||||
|
- `v1`
|
||||||
|
- `user`
|
||||||
|
- `appendMetadata(string, Any)`
|
||||||
|
The first parameter represents the key and the second a value which will be stored
|
||||||
|
|
||||||
|
## Post Creation
|
||||||
|
|
||||||
|
A user registers directly at ZITADEL.
|
||||||
|
ZITADEL successfully created the user.
|
||||||
|
|
||||||
|
### Parameters of Post Creation
|
||||||
|
|
||||||
|
- `ctx`
|
||||||
|
The first parameter contains the following fields
|
||||||
|
- `v1`
|
||||||
|
- `getUser()` [*user*](./objects#user)
|
||||||
|
- `authRequest` [*auth request*](/docs/apis/actions/objects#auth-request)
|
||||||
|
- `api`
|
||||||
|
The second parameter contains the following fields
|
||||||
|
- `userGrants` Array of [*userGrant*](./objects#user-grant)'s
|
||||||
|
- `v1`
|
||||||
|
- `appendUserGrant(`[`userGrant`](./objects#user-grant)`)`
|
@@ -46,8 +46,9 @@ Trigger types define the point during execution of request. Each trigger defines
|
|||||||
|
|
||||||
Currently ZITADEL provides the following flows:
|
Currently ZITADEL provides the following flows:
|
||||||
|
|
||||||
- [Login](./login-flow.md)
|
- [Internal Authentication](./internal-authentication.md)
|
||||||
- [Register](./register-flow.md)
|
- [External Authentication](./external-authentication.md)
|
||||||
|
- [Complement Token](./complement-token.md)
|
||||||
|
|
||||||
## Available Modules inside Javascript
|
## Available Modules inside Javascript
|
||||||
|
|
||||||
|
@@ -108,3 +108,48 @@ title: Objects
|
|||||||
- `phone`
|
- `phone`
|
||||||
- `phone` *string*
|
- `phone` *string*
|
||||||
- `isPhoneVerified` *boolean*
|
- `isPhoneVerified` *boolean*
|
||||||
|
|
||||||
|
## Auth Request
|
||||||
|
|
||||||
|
This object contains context information about the request to the [authorization endpoint](/docs/apis/openidoauth/endpoints#authorization_endpoint).
|
||||||
|
|
||||||
|
- `id` *string*
|
||||||
|
- `agentId` *string*
|
||||||
|
- `creationDate` *Date*
|
||||||
|
- `changeDate` *Date*
|
||||||
|
- `browserInfo` *browserInfo*
|
||||||
|
- `userAgent` *string*
|
||||||
|
- `acceptLanguage` *string*
|
||||||
|
- `remoteIp` *string*
|
||||||
|
- `applicationId` *string*
|
||||||
|
- `callbackUri` *string*
|
||||||
|
- `transferState` *string*
|
||||||
|
- `prompt` Array of *Number*
|
||||||
|
<ul><li>0: not specified</li><li>1: none</li><li>2: login</li><li>3: consent</li><li>4: select_account</li><li>5: create</li></ul>
|
||||||
|
- `uiLocales` Array of *string*
|
||||||
|
- `loginHint` *string*
|
||||||
|
- `maxAuthAge` *Number*
|
||||||
|
Duration in nanoseconds
|
||||||
|
- `instanceId` *string*
|
||||||
|
- `request`
|
||||||
|
- `oidc`
|
||||||
|
- `scopes` Array of *string*
|
||||||
|
- `userId` *string*
|
||||||
|
- `userName` *string*
|
||||||
|
- `loginName` *string*
|
||||||
|
- `displayName` *string*
|
||||||
|
- `resourceOwner` *string*
|
||||||
|
- `requestedOrgId` *string*
|
||||||
|
- `requestedOrgName` *string*
|
||||||
|
- `requestedPrimaryDomain` *string*
|
||||||
|
- `requestedOrgDomain` *bool*
|
||||||
|
- `applicationResourceOwner` *string*
|
||||||
|
- `privateLabelingSetting` *Number*
|
||||||
|
<ul><li>0: Unspecified</li><li>1: Enforce project resource owner policy</li><li>2: Allow login user resource owner policy</li></ul>
|
||||||
|
- `selectedIdpConfigId` *string*
|
||||||
|
- `linkingUsers` Array of [*ExternalUser*](#external-user)
|
||||||
|
- `passwordVerified` *bool*
|
||||||
|
- `mfasVerified` Array of *Number*
|
||||||
|
<ul><li>0: OTP</li><li>1: U2F</li><li>2: U2F User verification</li></ul>
|
||||||
|
- `audience` Array of *string*
|
||||||
|
- `authTime` *Date*
|
||||||
|
@@ -35,7 +35,7 @@ https://github.com/zitadel/actions/blob/main/examples/add_user_grant.js
|
|||||||
|
|
||||||
## Run the action when a user registers
|
## Run the action when a user registers
|
||||||
|
|
||||||
Now, make the action hook into the [external authentication flow](../../../apis/actions/register-flow#external-authentication).
|
Now, make the action hook into the [external authentication flow](../../../apis/actions/external-authentication).
|
||||||
|
|
||||||
1. In the **Flows <i className="las la-exchange-alt"></i>** section, select the **+ New** button.
|
1. In the **Flows <i className="las la-exchange-alt"></i>** section, select the **+ New** button.
|
||||||
1. Select the **Flow Type** _External Authentication_.
|
1. Select the **Flow Type** _External Authentication_.
|
||||||
|
@@ -232,8 +232,9 @@ module.exports = {
|
|||||||
items: [
|
items: [
|
||||||
"apis/actions/introduction",
|
"apis/actions/introduction",
|
||||||
"apis/actions/modules",
|
"apis/actions/modules",
|
||||||
"apis/actions/login-flow",
|
"apis/actions/internal-authentication",
|
||||||
"apis/actions/register-flow",
|
"apis/actions/external-authentication",
|
||||||
|
"apis/actions/complement-token",
|
||||||
"apis/actions/objects",
|
"apis/actions/objects",
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
1
e2e/.gitignore
vendored
1
e2e/.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
node_modules
|
node_modules
|
||||||
|
.cypress-cache
|
||||||
|
@@ -7,3 +7,6 @@ node_modules
|
|||||||
cypress/screenshots/
|
cypress/screenshots/
|
||||||
cypress/videos/
|
cypress/videos/
|
||||||
cypress/results/
|
cypress/results/
|
||||||
|
|
||||||
|
# not compiling code
|
||||||
|
cypress/e2e/actions/e2eSetMetadata.js
|
@@ -12,7 +12,6 @@ export default defineConfig({
|
|||||||
json: true,
|
json: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
chromeWebSecurity: false,
|
|
||||||
trashAssetsBeforeRuns: false,
|
trashAssetsBeforeRuns: false,
|
||||||
defaultCommandTimeout: 10000,
|
defaultCommandTimeout: 10000,
|
||||||
|
|
||||||
|
122
internal/actions/object/auth_request.go
Normal file
122
internal/actions/object/auth_request.go
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/dop251/goja"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/actions"
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthRequestField accepts the domain.AuthRequest by value, so its not mutated
|
||||||
|
func AuthRequestField(authRequest *domain.AuthRequest) func(c *actions.FieldConfig) interface{} {
|
||||||
|
return func(c *actions.FieldConfig) interface{} {
|
||||||
|
return AuthRequestFromDomain(c, authRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AuthRequestFromDomain(c *actions.FieldConfig, request *domain.AuthRequest) goja.Value {
|
||||||
|
return c.Runtime.ToValue(&authRequest{
|
||||||
|
Id: request.ID,
|
||||||
|
AgentId: request.AgentID,
|
||||||
|
CreationDate: request.CreationDate,
|
||||||
|
ChangeDate: request.ChangeDate,
|
||||||
|
BrowserInfo: &browserInfo{
|
||||||
|
UserAgent: request.BrowserInfo.UserAgent,
|
||||||
|
AcceptLanguage: request.BrowserInfo.AcceptLanguage,
|
||||||
|
RemoteIp: request.BrowserInfo.RemoteIP,
|
||||||
|
},
|
||||||
|
ApplicationId: request.ApplicationID,
|
||||||
|
CallbackUri: request.CallbackURI,
|
||||||
|
TransferState: request.TransferState,
|
||||||
|
Prompt: request.Prompt,
|
||||||
|
UiLocales: request.UiLocales,
|
||||||
|
LoginHint: request.LoginHint,
|
||||||
|
MaxAuthAge: request.MaxAuthAge,
|
||||||
|
InstanceId: request.InstanceID,
|
||||||
|
Request: requestFromDomain(request.Request),
|
||||||
|
UserId: request.UserID,
|
||||||
|
UserName: request.UserName,
|
||||||
|
LoginName: request.LoginName,
|
||||||
|
DisplayName: request.DisplayName,
|
||||||
|
ResourceOwner: request.UserOrgID,
|
||||||
|
RequestedOrgId: request.RequestedOrgID,
|
||||||
|
RequestedOrgName: request.RequestedOrgName,
|
||||||
|
RequestedPrimaryDomain: request.RequestedPrimaryDomain,
|
||||||
|
RequestedOrgDomain: request.RequestedOrgDomain,
|
||||||
|
ApplicationResourceOwner: request.ApplicationResourceOwner,
|
||||||
|
PrivateLabelingSetting: request.PrivateLabelingSetting,
|
||||||
|
SelectedIdpConfigId: request.SelectedIDPConfigID,
|
||||||
|
LinkingUsers: externalUsersFromDomain(request.LinkingUsers),
|
||||||
|
PasswordVerified: request.PasswordVerified,
|
||||||
|
MfasVerified: request.MFAsVerified,
|
||||||
|
Audience: request.Audience,
|
||||||
|
AuthTime: request.AuthTime,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type authRequest struct {
|
||||||
|
Id string
|
||||||
|
AgentId string
|
||||||
|
CreationDate time.Time
|
||||||
|
ChangeDate time.Time
|
||||||
|
BrowserInfo *browserInfo
|
||||||
|
ApplicationId string
|
||||||
|
CallbackUri string
|
||||||
|
TransferState string
|
||||||
|
Prompt []domain.Prompt
|
||||||
|
UiLocales []string
|
||||||
|
LoginHint string
|
||||||
|
MaxAuthAge *time.Duration
|
||||||
|
InstanceId string
|
||||||
|
Request *request
|
||||||
|
UserId string
|
||||||
|
UserName string
|
||||||
|
LoginName string
|
||||||
|
DisplayName string
|
||||||
|
// UserOrgID string
|
||||||
|
ResourceOwner string
|
||||||
|
// requested by scope
|
||||||
|
RequestedOrgId string
|
||||||
|
// requested by scope
|
||||||
|
RequestedOrgName string
|
||||||
|
// requested by scope
|
||||||
|
RequestedPrimaryDomain string
|
||||||
|
// requested by scope
|
||||||
|
RequestedOrgDomain bool
|
||||||
|
// client
|
||||||
|
ApplicationResourceOwner string
|
||||||
|
PrivateLabelingSetting domain.PrivateLabelingSetting
|
||||||
|
SelectedIdpConfigId string
|
||||||
|
LinkingUsers []*externalUser
|
||||||
|
PasswordVerified bool
|
||||||
|
MfasVerified []domain.MFAType
|
||||||
|
Audience []string
|
||||||
|
AuthTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestFromDomain(req domain.Request) *request {
|
||||||
|
r := new(request)
|
||||||
|
|
||||||
|
if oidcRequest, ok := req.(*domain.AuthRequestOIDC); ok {
|
||||||
|
r.Oidc = OIDCRequest{Scopes: oidcRequest.Scopes}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
type request struct {
|
||||||
|
Oidc OIDCRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
type OIDCRequest struct {
|
||||||
|
Scopes []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type browserInfo struct {
|
||||||
|
UserAgent string
|
||||||
|
AcceptLanguage string
|
||||||
|
RemoteIp net.IP
|
||||||
|
}
|
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/zitadel/logging"
|
"github.com/zitadel/logging"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/actions"
|
"github.com/zitadel/zitadel/internal/actions"
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -54,3 +55,87 @@ type userMetadata struct {
|
|||||||
Key string
|
Key string
|
||||||
Value goja.Value
|
Value goja.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MetadataList struct {
|
||||||
|
Metadata []*Metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
type Metadata struct {
|
||||||
|
Key string
|
||||||
|
// Value is for exporting to javascript
|
||||||
|
Value goja.Value
|
||||||
|
// value is for mapping to [domain.Metadata]
|
||||||
|
value []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (md *MetadataList) AppendMetadataFunc(call goja.FunctionCall) goja.Value {
|
||||||
|
if len(call.Arguments) != 2 {
|
||||||
|
panic("exactly 2 (key, value) arguments expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := json.Marshal(call.Arguments[1].Export())
|
||||||
|
if err != nil {
|
||||||
|
logging.WithError(err).Debug("unable to marshal")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
md.Metadata = append(md.Metadata,
|
||||||
|
&Metadata{
|
||||||
|
Key: call.Arguments[0].Export().(string),
|
||||||
|
Value: call.Arguments[1],
|
||||||
|
value: value,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func MetadataListToDomain(metadataList *MetadataList) []*domain.Metadata {
|
||||||
|
if metadataList == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
list := make([]*domain.Metadata, len(metadataList.Metadata))
|
||||||
|
for i, metadata := range metadataList.Metadata {
|
||||||
|
list[i] = &domain.Metadata{
|
||||||
|
Key: metadata.Key,
|
||||||
|
Value: metadata.value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func MetadataField(metadata *MetadataList) func(c *actions.FieldConfig) interface{} {
|
||||||
|
return func(c *actions.FieldConfig) interface{} {
|
||||||
|
for _, md := range metadata.Metadata {
|
||||||
|
if json.Valid(md.value) {
|
||||||
|
err := json.Unmarshal(md.value, &md.Value)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata.Metadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MetadataListFromDomain(metadata []*domain.Metadata) *MetadataList {
|
||||||
|
list := &MetadataList{Metadata: make([]*Metadata, len(metadata))}
|
||||||
|
|
||||||
|
for i, md := range metadata {
|
||||||
|
var val interface{}
|
||||||
|
if json.Valid(md.Value) {
|
||||||
|
err := json.Unmarshal(md.Value, &val)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Metadata[i] = &Metadata{
|
||||||
|
Key: md.Key,
|
||||||
|
value: md.Value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
14
internal/actions/object/object.go
Normal file
14
internal/actions/object/object.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package object
|
||||||
|
|
||||||
|
import "github.com/dop251/goja"
|
||||||
|
|
||||||
|
func objectFromFirstArgument(call goja.FunctionCall, runtime *goja.Runtime) *goja.Object {
|
||||||
|
if len(call.Arguments) != 1 {
|
||||||
|
panic("exactly one argument expected")
|
||||||
|
}
|
||||||
|
object := call.Arguments[0].ToObject(runtime)
|
||||||
|
if object == nil {
|
||||||
|
panic("unable to unmarshal arg")
|
||||||
|
}
|
||||||
|
return object
|
||||||
|
}
|
@@ -11,7 +11,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func UserFromExternalUser(c *actions.FieldConfig, user *domain.ExternalUser) goja.Value {
|
func UserFromExternalUser(c *actions.FieldConfig, user *domain.ExternalUser) goja.Value {
|
||||||
return c.Runtime.ToValue(&externalUser{
|
return c.Runtime.ToValue(externalUserFromDomain(user))
|
||||||
|
}
|
||||||
|
|
||||||
|
func externalUsersFromDomain(users []*domain.ExternalUser) []*externalUser {
|
||||||
|
externalUsers := make([]*externalUser, len(users))
|
||||||
|
|
||||||
|
for i, user := range users {
|
||||||
|
externalUsers[i] = externalUserFromDomain(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
return externalUsers
|
||||||
|
}
|
||||||
|
|
||||||
|
func externalUserFromDomain(user *domain.ExternalUser) *externalUser {
|
||||||
|
return &externalUser{
|
||||||
ExternalId: user.ExternalUserID,
|
ExternalId: user.ExternalUserID,
|
||||||
ExternalIdpId: user.ExternalUserID,
|
ExternalIdpId: user.ExternalUserID,
|
||||||
Human: human{
|
Human: human{
|
||||||
@@ -25,7 +39,7 @@ func UserFromExternalUser(c *actions.FieldConfig, user *domain.ExternalUser) goj
|
|||||||
Phone: user.Phone,
|
Phone: user.Phone,
|
||||||
IsPhoneVerified: user.IsPhoneVerified,
|
IsPhoneVerified: user.IsPhoneVerified,
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UserFromHuman(c *actions.FieldConfig, user *domain.Human) goja.Value {
|
func UserFromHuman(c *actions.FieldConfig, user *domain.Human) goja.Value {
|
||||||
@@ -95,6 +109,7 @@ func humanFromQuery(c *actions.FieldConfig, user *query.User) goja.Value {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func machineFromQuery(c *actions.FieldConfig, user *query.User) goja.Value {
|
func machineFromQuery(c *actions.FieldConfig, user *query.User) goja.Value {
|
||||||
return c.Runtime.ToValue(&machineUser{
|
return c.Runtime.ToValue(&machineUser{
|
||||||
Id: user.ID,
|
Id: user.ID,
|
||||||
|
68
internal/actions/object/user_grant.go
Normal file
68
internal/actions/object/user_grant.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/dop251/goja"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/actions"
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserGrants struct {
|
||||||
|
UserGrants []UserGrant
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserGrant struct {
|
||||||
|
ProjectID string
|
||||||
|
ProjectGrantID string
|
||||||
|
Roles []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppendGrantFunc(userGrants *UserGrants) func(c *actions.FieldConfig) func(call goja.FunctionCall) goja.Value {
|
||||||
|
return func(c *actions.FieldConfig) func(call goja.FunctionCall) goja.Value {
|
||||||
|
return func(call goja.FunctionCall) goja.Value {
|
||||||
|
firstArg := objectFromFirstArgument(call, c.Runtime)
|
||||||
|
grant := UserGrant{}
|
||||||
|
mapObjectToGrant(firstArg, &grant)
|
||||||
|
userGrants.UserGrants = append(userGrants.UserGrants, grant)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapObjectToGrant(object *goja.Object, grant *UserGrant) {
|
||||||
|
for _, key := range object.Keys() {
|
||||||
|
switch key {
|
||||||
|
case "projectId":
|
||||||
|
grant.ProjectID = object.Get(key).String()
|
||||||
|
case "projectGrantId":
|
||||||
|
grant.ProjectGrantID = object.Get(key).String()
|
||||||
|
case "roles":
|
||||||
|
if roles, ok := object.Get(key).Export().([]interface{}); ok {
|
||||||
|
for _, role := range roles {
|
||||||
|
if r, ok := role.(string); ok {
|
||||||
|
grant.Roles = append(grant.Roles, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if grant.ProjectID == "" {
|
||||||
|
panic("projectId not set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserGrantsToDomain(userID string, actionUserGrants []UserGrant) []*domain.UserGrant {
|
||||||
|
if actionUserGrants == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
userGrants := make([]*domain.UserGrant, len(actionUserGrants))
|
||||||
|
for i, grant := range actionUserGrants {
|
||||||
|
userGrants[i] = &domain.UserGrant{
|
||||||
|
UserID: userID,
|
||||||
|
ProjectID: grant.ProjectID,
|
||||||
|
ProjectGrantID: grant.ProjectGrantID,
|
||||||
|
RoleKeys: grant.Roles,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return userGrants
|
||||||
|
}
|
@@ -1,7 +0,0 @@
|
|||||||
package actions
|
|
||||||
|
|
||||||
type UserGrant struct {
|
|
||||||
ProjectID string
|
|
||||||
ProjectGrantID string
|
|
||||||
Roles []string
|
|
||||||
}
|
|
@@ -17,6 +17,8 @@ func FlowTypeToDomain(flowType string) domain.FlowType {
|
|||||||
return domain.FlowTypeExternalAuthentication
|
return domain.FlowTypeExternalAuthentication
|
||||||
case domain.FlowTypeCustomiseToken.ID():
|
case domain.FlowTypeCustomiseToken.ID():
|
||||||
return domain.FlowTypeCustomiseToken
|
return domain.FlowTypeCustomiseToken
|
||||||
|
case domain.FlowTypeInternalAuthentication.ID():
|
||||||
|
return domain.FlowTypeInternalAuthentication
|
||||||
default:
|
default:
|
||||||
return domain.FlowTypeUnspecified
|
return domain.FlowTypeUnspecified
|
||||||
}
|
}
|
||||||
|
@@ -659,7 +659,7 @@ func (s *Server) getUsers(ctx context.Context, org string, withPasswords bool, w
|
|||||||
func (s *Server) getTriggerActions(ctx context.Context, org string, processedActions []string) (_ []*management_pb.SetTriggerActionsRequest, err error) {
|
func (s *Server) getTriggerActions(ctx context.Context, org string, processedActions []string) (_ []*management_pb.SetTriggerActionsRequest, err error) {
|
||||||
ctx, span := tracing.NewSpan(ctx)
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
defer func() { span.EndWithError(err) }()
|
defer func() { span.EndWithError(err) }()
|
||||||
flowTypes := []domain.FlowType{domain.FlowTypeExternalAuthentication}
|
flowTypes := []domain.FlowType{domain.FlowTypeExternalAuthentication, domain.FlowTypeInternalAuthentication}
|
||||||
triggerActions := make([]*management_pb.SetTriggerActionsRequest, 0)
|
triggerActions := make([]*management_pb.SetTriggerActionsRequest, 0)
|
||||||
|
|
||||||
for _, flowType := range flowTypes {
|
for _, flowType := range flowTypes {
|
||||||
|
@@ -17,6 +17,7 @@ func (s *Server) ListFlowTypes(ctx context.Context, _ *mgmt_pb.ListFlowTypesRequ
|
|||||||
Result: []*action_pb.FlowType{
|
Result: []*action_pb.FlowType{
|
||||||
action_grpc.FlowTypeToPb(domain.FlowTypeExternalAuthentication),
|
action_grpc.FlowTypeToPb(domain.FlowTypeExternalAuthentication),
|
||||||
action_grpc.FlowTypeToPb(domain.FlowTypeCustomiseToken),
|
action_grpc.FlowTypeToPb(domain.FlowTypeCustomiseToken),
|
||||||
|
action_grpc.FlowTypeToPb(domain.FlowTypeInternalAuthentication),
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/dop251/goja"
|
"github.com/dop251/goja"
|
||||||
"github.com/zitadel/logging"
|
|
||||||
"github.com/zitadel/oidc/v2/pkg/oidc"
|
"github.com/zitadel/oidc/v2/pkg/oidc"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
@@ -30,25 +29,7 @@ func (l *Login) customExternalUserMapping(ctx context.Context, user *domain.Exte
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctxFields := actions.SetContextFields(
|
metadataList := object.MetadataListFromDomain(user.Metadatas)
|
||||||
actions.SetFields("accessToken", tokens.AccessToken),
|
|
||||||
actions.SetFields("idToken", tokens.IDToken),
|
|
||||||
actions.SetFields("getClaim", func(claim string) interface{} {
|
|
||||||
return tokens.IDTokenClaims.GetClaim(claim)
|
|
||||||
}),
|
|
||||||
actions.SetFields("claimsJSON", func() (string, error) {
|
|
||||||
c, err := json.Marshal(tokens.IDTokenClaims)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(c), nil
|
|
||||||
}),
|
|
||||||
actions.SetFields("v1",
|
|
||||||
actions.SetFields("externalUser", func(c *actions.FieldConfig) interface{} {
|
|
||||||
return object.UserFromExternalUser(c, user)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
apiFields := actions.WithAPIFields(
|
apiFields := actions.WithAPIFields(
|
||||||
actions.SetFields("setFirstName", func(firstName string) {
|
actions.SetFields("setFirstName", func(firstName string) {
|
||||||
user.FirstName = firstName
|
user.FirstName = firstName
|
||||||
@@ -80,35 +61,28 @@ func (l *Login) customExternalUserMapping(ctx context.Context, user *domain.Exte
|
|||||||
actions.SetFields("setPhoneVerified", func(verified bool) {
|
actions.SetFields("setPhoneVerified", func(verified bool) {
|
||||||
user.IsPhoneVerified = verified
|
user.IsPhoneVerified = verified
|
||||||
}),
|
}),
|
||||||
actions.SetFields("metadata", &user.Metadatas),
|
actions.SetFields("metadata", &metadataList.Metadata),
|
||||||
actions.SetFields("v1",
|
actions.SetFields("v1",
|
||||||
actions.SetFields("user",
|
actions.SetFields("user",
|
||||||
actions.SetFields("appendMetadata", func(call goja.FunctionCall) goja.Value {
|
actions.SetFields("appendMetadata", metadataList.AppendMetadataFunc),
|
||||||
if len(call.Arguments) != 2 {
|
|
||||||
panic("exactly 2 (key, value) arguments expected")
|
|
||||||
}
|
|
||||||
key := call.Arguments[0].Export().(string)
|
|
||||||
val := call.Arguments[1].Export()
|
|
||||||
|
|
||||||
value, err := json.Marshal(val)
|
|
||||||
if err != nil {
|
|
||||||
logging.WithError(err).Debug("unable to marshal")
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
user.Metadatas = append(user.Metadatas,
|
|
||||||
&domain.Metadata{
|
|
||||||
Key: key,
|
|
||||||
Value: value,
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, a := range triggerActions {
|
for _, a := range triggerActions {
|
||||||
actionCtx, cancel := context.WithTimeout(ctx, a.Timeout())
|
actionCtx, cancel := context.WithTimeout(ctx, a.Timeout())
|
||||||
|
|
||||||
|
ctxFieldOptions := append(tokenCtxFields(tokens),
|
||||||
|
actions.SetFields("v1",
|
||||||
|
actions.SetFields("externalUser", func(c *actions.FieldConfig) interface{} {
|
||||||
|
return object.UserFromExternalUser(c, user)
|
||||||
|
}),
|
||||||
|
actions.SetFields("authRequest", object.AuthRequestField(req)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
ctxFields := actions.SetContextFields(ctxFieldOptions...)
|
||||||
|
|
||||||
err = actions.Run(
|
err = actions.Run(
|
||||||
actionCtx,
|
actionCtx,
|
||||||
ctxFields,
|
ctxFields,
|
||||||
@@ -122,22 +96,78 @@ func (l *Login) customExternalUserMapping(ctx context.Context, user *domain.Exte
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
user.Metadatas = object.MetadataListToDomain(metadataList)
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Login) customExternalUserToLoginUserMapping(ctx context.Context, user *domain.Human, tokens *oidc.Tokens, req *domain.AuthRequest, config *iam_model.IDPConfigView, metadata []*domain.Metadata, resourceOwner string) (*domain.Human, []*domain.Metadata, error) {
|
type authMethod string
|
||||||
triggerActions, err := l.query.GetActiveActionsByFlowAndTriggerType(ctx, domain.FlowTypeExternalAuthentication, domain.TriggerTypePreCreation, resourceOwner, false)
|
|
||||||
|
const (
|
||||||
|
authMethodPassword authMethod = "password"
|
||||||
|
authMethodOTP authMethod = "OTP"
|
||||||
|
authMethodU2F authMethod = "U2F"
|
||||||
|
authMethodPasswordless authMethod = "passwordless"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (l *Login) triggerPostLocalAuthentication(ctx context.Context, req *domain.AuthRequest, authMethod authMethod, authenticationError error) ([]*domain.Metadata, error) {
|
||||||
|
resourceOwner := req.RequestedOrgID
|
||||||
|
if resourceOwner == "" {
|
||||||
|
resourceOwner = req.UserOrgID
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerActions, err := l.query.GetActiveActionsByFlowAndTriggerType(ctx, domain.FlowTypeInternalAuthentication, domain.TriggerTypePostAuthentication, resourceOwner, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
metadataList := object.MetadataListFromDomain(nil)
|
||||||
|
apiFields := actions.WithAPIFields(
|
||||||
|
actions.SetFields("metadata", &metadataList.Metadata),
|
||||||
|
actions.SetFields("v1",
|
||||||
|
actions.SetFields("user",
|
||||||
|
actions.SetFields("appendMetadata", metadataList.AppendMetadataFunc),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for _, a := range triggerActions {
|
||||||
|
actionCtx, cancel := context.WithTimeout(ctx, a.Timeout())
|
||||||
|
|
||||||
|
authErrStr := "none"
|
||||||
|
if authenticationError != nil {
|
||||||
|
authErrStr = authenticationError.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
ctxFields := actions.SetContextFields(
|
||||||
|
actions.SetFields("v1",
|
||||||
|
actions.SetFields("authMethod", authMethod),
|
||||||
|
actions.SetFields("authError", authErrStr),
|
||||||
|
actions.SetFields("authRequest", object.AuthRequestField(req)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
err = actions.Run(
|
||||||
|
actionCtx,
|
||||||
|
ctxFields,
|
||||||
|
apiFields,
|
||||||
|
a.Script,
|
||||||
|
a.Name,
|
||||||
|
append(actions.ActionToOptions(a), actions.WithHTTP(actionCtx), actions.WithLogger(actions.ServerLog))...,
|
||||||
|
)
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return object.MetadataListToDomain(metadataList), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) customUserToLoginUserMapping(ctx context.Context, authRequest *domain.AuthRequest, user *domain.Human, metadata []*domain.Metadata, resourceOwner string, flowType domain.FlowType) (*domain.Human, []*domain.Metadata, error) {
|
||||||
|
triggerActions, err := l.query.GetActiveActionsByFlowAndTriggerType(ctx, flowType, domain.TriggerTypePreCreation, resourceOwner, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctxOpts := actions.SetContextFields(
|
metadataList := object.MetadataListFromDomain(metadata)
|
||||||
actions.SetFields("v1",
|
|
||||||
actions.SetFields("user", func(c *actions.FieldConfig) interface{} {
|
|
||||||
return object.UserFromHuman(c, user)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
apiFields := actions.WithAPIFields(
|
apiFields := actions.WithAPIFields(
|
||||||
actions.SetFields("setFirstName", func(firstName string) {
|
actions.SetFields("setFirstName", func(firstName string) {
|
||||||
user.FirstName = firstName
|
user.FirstName = firstName
|
||||||
@@ -184,35 +214,26 @@ func (l *Login) customExternalUserToLoginUserMapping(ctx context.Context, user *
|
|||||||
}
|
}
|
||||||
user.Phone.IsPhoneVerified = verified
|
user.Phone.IsPhoneVerified = verified
|
||||||
}),
|
}),
|
||||||
actions.SetFields("metadata", metadata),
|
actions.SetFields("metadata", &metadataList.Metadata),
|
||||||
actions.SetFields("v1",
|
actions.SetFields("v1",
|
||||||
actions.SetFields("user",
|
actions.SetFields("user",
|
||||||
actions.SetFields("appendMetadata", func(call goja.FunctionCall) goja.Value {
|
actions.SetFields("appendMetadata", metadataList.AppendMetadataFunc),
|
||||||
if len(call.Arguments) != 2 {
|
|
||||||
panic("exactly 2 (key, value) arguments expected")
|
|
||||||
}
|
|
||||||
key := call.Arguments[0].Export().(string)
|
|
||||||
val := call.Arguments[1].Export()
|
|
||||||
|
|
||||||
value, err := json.Marshal(val)
|
|
||||||
if err != nil {
|
|
||||||
logging.WithError(err).Debug("unable to marshal")
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
metadata = append(metadata,
|
|
||||||
&domain.Metadata{
|
|
||||||
Key: key,
|
|
||||||
Value: value,
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, a := range triggerActions {
|
for _, a := range triggerActions {
|
||||||
actionCtx, cancel := context.WithTimeout(ctx, a.Timeout())
|
actionCtx, cancel := context.WithTimeout(ctx, a.Timeout())
|
||||||
|
|
||||||
|
ctxOpts := actions.SetContextFields(
|
||||||
|
actions.SetFields("v1",
|
||||||
|
actions.SetFields("user", func(c *actions.FieldConfig) interface{} {
|
||||||
|
return object.UserFromHuman(c, user)
|
||||||
|
}),
|
||||||
|
actions.SetFields("authRequest", object.AuthRequestField(authRequest)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
err = actions.Run(
|
err = actions.Run(
|
||||||
actionCtx,
|
actionCtx,
|
||||||
ctxOpts,
|
ctxOpts,
|
||||||
@@ -226,57 +247,21 @@ func (l *Login) customExternalUserToLoginUserMapping(ctx context.Context, user *
|
|||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return user, metadata, err
|
return user, object.MetadataListToDomain(metadataList), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Login) customGrants(ctx context.Context, userID string, tokens *oidc.Tokens, req *domain.AuthRequest, config *iam_model.IDPConfigView, resourceOwner string) ([]*domain.UserGrant, error) {
|
func (l *Login) customGrants(ctx context.Context, userID string, authRequest *domain.AuthRequest, resourceOwner string, flowType domain.FlowType) ([]*domain.UserGrant, error) {
|
||||||
triggerActions, err := l.query.GetActiveActionsByFlowAndTriggerType(ctx, domain.FlowTypeExternalAuthentication, domain.TriggerTypePostCreation, resourceOwner, false)
|
triggerActions, err := l.query.GetActiveActionsByFlowAndTriggerType(ctx, flowType, domain.TriggerTypePostCreation, resourceOwner, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
actionUserGrants := make([]actions.UserGrant, 0)
|
mutableUserGrants := &object.UserGrants{UserGrants: make([]object.UserGrant, 0)}
|
||||||
|
|
||||||
apiFields := actions.WithAPIFields(
|
apiFields := actions.WithAPIFields(
|
||||||
actions.SetFields("userGrants", &actionUserGrants),
|
actions.SetFields("userGrants", &mutableUserGrants.UserGrants),
|
||||||
actions.SetFields("v1",
|
actions.SetFields("v1",
|
||||||
actions.SetFields("appendUserGrant", func(c *actions.FieldConfig) interface{} {
|
actions.SetFields("appendUserGrant", object.AppendGrantFunc(mutableUserGrants)),
|
||||||
return func(call goja.FunctionCall) goja.Value {
|
|
||||||
if len(call.Arguments) != 1 {
|
|
||||||
panic("exactly one argument expected")
|
|
||||||
}
|
|
||||||
object := call.Arguments[0].ToObject(c.Runtime)
|
|
||||||
if object == nil {
|
|
||||||
panic("unable to unmarshal arg")
|
|
||||||
}
|
|
||||||
grant := actions.UserGrant{}
|
|
||||||
|
|
||||||
for _, key := range object.Keys() {
|
|
||||||
switch key {
|
|
||||||
case "projectId":
|
|
||||||
grant.ProjectID = object.Get(key).String()
|
|
||||||
case "projectGrantId":
|
|
||||||
grant.ProjectGrantID = object.Get(key).String()
|
|
||||||
case "roles":
|
|
||||||
if roles, ok := object.Get(key).Export().([]interface{}); ok {
|
|
||||||
for _, role := range roles {
|
|
||||||
if r, ok := role.(string); ok {
|
|
||||||
grant.Roles = append(grant.Roles, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if grant.ProjectID == "" {
|
|
||||||
panic("projectId not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
actionUserGrants = append(actionUserGrants, grant)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -287,13 +272,14 @@ func (l *Login) customGrants(ctx context.Context, userID string, tokens *oidc.To
|
|||||||
actions.SetFields("v1",
|
actions.SetFields("v1",
|
||||||
actions.SetFields("getUser", func(c *actions.FieldConfig) interface{} {
|
actions.SetFields("getUser", func(c *actions.FieldConfig) interface{} {
|
||||||
return func(call goja.FunctionCall) goja.Value {
|
return func(call goja.FunctionCall) goja.Value {
|
||||||
user, err := l.query.GetUserByID(actionCtx, true, userID, false)
|
user, err := l.query.GetUserByID(actionCtx, true, authRequest.UserID, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return object.UserFromQuery(c, user)
|
return object.UserFromQuery(c, user)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
actions.SetFields("authRequest", object.AuthRequestField(authRequest)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -310,21 +296,22 @@ func (l *Login) customGrants(ctx context.Context, userID string, tokens *oidc.To
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return actionUserGrantsToDomain(userID, actionUserGrants), err
|
return object.UserGrantsToDomain(userID, mutableUserGrants.UserGrants), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func actionUserGrantsToDomain(userID string, actionUserGrants []actions.UserGrant) []*domain.UserGrant {
|
func tokenCtxFields(tokens *oidc.Tokens) []actions.FieldOption {
|
||||||
if actionUserGrants == nil {
|
return []actions.FieldOption{
|
||||||
return nil
|
actions.SetFields("accessToken", tokens.AccessToken),
|
||||||
|
actions.SetFields("idToken", tokens.IDToken),
|
||||||
|
actions.SetFields("getClaim", func(claim string) interface{} {
|
||||||
|
return tokens.IDTokenClaims.GetClaim(claim)
|
||||||
|
}),
|
||||||
|
actions.SetFields("claimsJSON", func() (string, error) {
|
||||||
|
c, err := json.Marshal(tokens.IDTokenClaims)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(c), nil
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
userGrants := make([]*domain.UserGrant, len(actionUserGrants))
|
|
||||||
for i, grant := range actionUserGrants {
|
|
||||||
userGrants[i] = &domain.UserGrant{
|
|
||||||
UserID: userID,
|
|
||||||
ProjectID: grant.ProjectID,
|
|
||||||
ProjectGrantID: grant.ProjectGrantID,
|
|
||||||
RoleKeys: grant.Roles,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return userGrants
|
|
||||||
}
|
}
|
||||||
|
@@ -383,7 +383,7 @@ func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authR
|
|||||||
|
|
||||||
user, externalIDP, metadata := l.mapExternalUserToLoginUser(orgIamPolicy, linkingUser, idpConfig)
|
user, externalIDP, metadata := l.mapExternalUserToLoginUser(orgIamPolicy, linkingUser, idpConfig)
|
||||||
|
|
||||||
user, metadata, err = l.customExternalUserToLoginUserMapping(r.Context(), user, nil, authReq, idpConfig, metadata, resourceOwner)
|
user, metadata, err = l.customUserToLoginUserMapping(r.Context(), authReq, user, metadata, resourceOwner, domain.FlowTypeExternalAuthentication)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.renderExternalNotFoundOption(w, r, authReq, orgIamPolicy, nil, nil, err)
|
l.renderExternalNotFoundOption(w, r, authReq, orgIamPolicy, nil, nil, err)
|
||||||
return
|
return
|
||||||
@@ -398,7 +398,7 @@ func (l *Login) handleAutoRegister(w http.ResponseWriter, r *http.Request, authR
|
|||||||
l.renderError(w, r, authReq, err)
|
l.renderError(w, r, authReq, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userGrants, err := l.customGrants(r.Context(), authReq.UserID, nil, authReq, idpConfig, resourceOwner)
|
userGrants, err := l.customGrants(r.Context(), authReq.UserID, authReq, resourceOwner, domain.FlowTypeExternalAuthentication)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.renderError(w, r, authReq, err)
|
l.renderError(w, r, authReq, err)
|
||||||
return
|
return
|
||||||
|
@@ -161,7 +161,7 @@ func (l *Login) registerExternalUser(w http.ResponseWriter, r *http.Request, aut
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
user, externalIDP, metadata := l.mapExternalUserToLoginUser(orgIamPolicy, externalUser, idpConfig)
|
user, externalIDP, metadata := l.mapExternalUserToLoginUser(orgIamPolicy, externalUser, idpConfig)
|
||||||
user, metadata, err = l.customExternalUserToLoginUserMapping(r.Context(), user, nil, authReq, idpConfig, metadata, resourceOwner)
|
user, metadata, err = l.customUserToLoginUserMapping(r.Context(), authReq, user, metadata, resourceOwner, domain.FlowTypeExternalAuthentication)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.renderRegisterOption(w, r, authReq, err)
|
l.renderRegisterOption(w, r, authReq, err)
|
||||||
return
|
return
|
||||||
@@ -177,7 +177,7 @@ func (l *Login) registerExternalUser(w http.ResponseWriter, r *http.Request, aut
|
|||||||
l.renderError(w, r, authReq, err)
|
l.renderError(w, r, authReq, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userGrants, err := l.customGrants(r.Context(), authReq.UserID, nil, authReq, idpConfig, resourceOwner)
|
userGrants, err := l.customGrants(r.Context(), authReq.UserID, authReq, resourceOwner, domain.FlowTypeExternalAuthentication)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.renderError(w, r, authReq, err)
|
l.renderError(w, r, authReq, err)
|
||||||
return
|
return
|
||||||
|
@@ -129,7 +129,7 @@ func (l *Login) jwtExtractionUserNotFound(w http.ResponseWriter, r *http.Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
user, externalIDP, metadata := l.mapExternalUserToLoginUser(orgIamPolicy, authReq.LinkingUsers[len(authReq.LinkingUsers)-1], idpConfig)
|
user, externalIDP, metadata := l.mapExternalUserToLoginUser(orgIamPolicy, authReq.LinkingUsers[len(authReq.LinkingUsers)-1], idpConfig)
|
||||||
user, metadata, err = l.customExternalUserToLoginUserMapping(r.Context(), user, tokens, authReq, idpConfig, metadata, resourceOwner)
|
user, metadata, err = l.customUserToLoginUserMapping(r.Context(), authReq, user, metadata, resourceOwner, domain.FlowTypeExternalAuthentication)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.renderError(w, r, authReq, err)
|
l.renderError(w, r, authReq, err)
|
||||||
return
|
return
|
||||||
@@ -144,7 +144,7 @@ func (l *Login) jwtExtractionUserNotFound(w http.ResponseWriter, r *http.Request
|
|||||||
l.renderError(w, r, authReq, err)
|
l.renderError(w, r, authReq, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userGrants, err := l.customGrants(r.Context(), authReq.UserID, tokens, authReq, idpConfig, resourceOwner)
|
userGrants, err := l.customGrants(r.Context(), authReq.UserID, authReq, resourceOwner, domain.FlowTypeExternalAuthentication)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.renderError(w, r, authReq, err)
|
l.renderError(w, r, authReq, err)
|
||||||
return
|
return
|
||||||
|
@@ -73,7 +73,6 @@ func CreateLogin(config Config,
|
|||||||
idpConfigAlg crypto.EncryptionAlgorithm,
|
idpConfigAlg crypto.EncryptionAlgorithm,
|
||||||
csrfCookieKey []byte,
|
csrfCookieKey []byte,
|
||||||
) (*Login, error) {
|
) (*Login, error) {
|
||||||
|
|
||||||
login := &Login{
|
login := &Login{
|
||||||
oidcAuthCallbackURL: oidcAuthCallbackURL,
|
oidcAuthCallbackURL: oidcAuthCallbackURL,
|
||||||
samlAuthCallbackURL: samlAuthCallbackURL,
|
samlAuthCallbackURL: samlAuthCallbackURL,
|
||||||
|
@@ -36,6 +36,14 @@ func (l *Login) handleMFAVerify(w http.ResponseWriter, r *http.Request) {
|
|||||||
if data.MFAType == domain.MFATypeOTP {
|
if data.MFAType == domain.MFATypeOTP {
|
||||||
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
|
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
|
||||||
err = l.authRepo.VerifyMFAOTP(setContext(r.Context(), authReq.UserOrgID), authReq.ID, authReq.UserID, authReq.UserOrgID, data.Code, userAgentID, domain.BrowserInfoFromRequest(r))
|
err = l.authRepo.VerifyMFAOTP(setContext(r.Context(), authReq.UserOrgID), authReq.ID, authReq.UserID, authReq.UserOrgID, data.Code, userAgentID, domain.BrowserInfoFromRequest(r))
|
||||||
|
|
||||||
|
metadata, actionErr := l.triggerPostLocalAuthentication(r.Context(), authReq, authMethodOTP, err)
|
||||||
|
if err == nil && actionErr == nil && len(metadata) > 0 {
|
||||||
|
_, err = l.command.BulkSetUserMetadata(r.Context(), authReq.UserID, authReq.UserOrgID, metadata...)
|
||||||
|
} else if actionErr != nil && err == nil {
|
||||||
|
err = actionErr
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.renderMFAVerifySelected(w, r, authReq, step, domain.MFATypeOTP, err)
|
l.renderMFAVerifySelected(w, r, authReq, step, domain.MFATypeOTP, err)
|
||||||
return
|
return
|
||||||
|
@@ -71,6 +71,14 @@ func (l *Login) handleU2FVerification(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
|
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
|
||||||
err = l.authRepo.VerifyMFAU2F(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, authReq.ID, userAgentID, credData, domain.BrowserInfoFromRequest(r))
|
err = l.authRepo.VerifyMFAU2F(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, authReq.ID, userAgentID, credData, domain.BrowserInfoFromRequest(r))
|
||||||
|
|
||||||
|
metadata, actionErr := l.triggerPostLocalAuthentication(r.Context(), authReq, authMethodU2F, err)
|
||||||
|
if err == nil && actionErr == nil && len(metadata) > 0 {
|
||||||
|
_, err = l.command.BulkSetUserMetadata(r.Context(), authReq.UserID, authReq.UserOrgID, metadata...)
|
||||||
|
} else if actionErr != nil && err == nil {
|
||||||
|
err = actionErr
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.renderU2FVerification(w, r, authReq, step.MFAProviders, err)
|
l.renderU2FVerification(w, r, authReq, step.MFAProviders, err)
|
||||||
return
|
return
|
||||||
|
@@ -39,6 +39,14 @@ func (l *Login) handlePasswordCheck(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = l.authRepo.VerifyPassword(setContext(r.Context(), authReq.UserOrgID), authReq.ID, authReq.UserID, authReq.UserOrgID, data.Password, authReq.AgentID, domain.BrowserInfoFromRequest(r))
|
err = l.authRepo.VerifyPassword(setContext(r.Context(), authReq.UserOrgID), authReq.ID, authReq.UserID, authReq.UserOrgID, data.Password, authReq.AgentID, domain.BrowserInfoFromRequest(r))
|
||||||
|
|
||||||
|
metadata, actionErr := l.triggerPostLocalAuthentication(r.Context(), authReq, authMethodPassword, err)
|
||||||
|
if err == nil && actionErr == nil && len(metadata) > 0 {
|
||||||
|
_, err = l.command.BulkSetUserMetadata(r.Context(), authReq.UserID, authReq.UserOrgID, metadata...)
|
||||||
|
} else if actionErr != nil && err == nil {
|
||||||
|
err = actionErr
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if authReq.LoginPolicy.IgnoreUnknownUsernames {
|
if authReq.LoginPolicy.IgnoreUnknownUsernames {
|
||||||
l.renderLogin(w, r, authReq, err)
|
l.renderLogin(w, r, authReq, err)
|
||||||
|
@@ -63,6 +63,14 @@ func (l *Login) handlePasswordlessVerification(w http.ResponseWriter, r *http.Re
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = l.authRepo.VerifyPasswordless(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, authReq.ID, authReq.AgentID, credData, domain.BrowserInfoFromRequest(r))
|
err = l.authRepo.VerifyPasswordless(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, authReq.ID, authReq.AgentID, credData, domain.BrowserInfoFromRequest(r))
|
||||||
|
|
||||||
|
metadata, actionErr := l.triggerPostLocalAuthentication(r.Context(), authReq, authMethodPasswordless, err)
|
||||||
|
if err == nil && actionErr == nil && len(metadata) > 0 {
|
||||||
|
_, err = l.command.BulkSetUserMetadata(r.Context(), authReq.UserID, authReq.UserOrgID, metadata...)
|
||||||
|
} else if actionErr != nil && err == nil {
|
||||||
|
err = actionErr
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.renderPasswordlessVerification(w, r, authReq, formData.PasswordLogin, err)
|
l.renderPasswordlessVerification(w, r, authReq, formData.PasswordLogin, err)
|
||||||
return
|
return
|
||||||
|
@@ -82,11 +82,48 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) {
|
|||||||
l.renderRegister(w, r, authRequest, data, err)
|
l.renderRegister(w, r, authRequest, data, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user, err := l.command.RegisterHuman(setContext(r.Context(), resourceOwner), resourceOwner, data.toHumanDomain(), nil, nil, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator)
|
|
||||||
|
// For consistency with the external authentication flow,
|
||||||
|
// the setMetadata() function is provided on the pre creation hook, for now,
|
||||||
|
// like for the ExternalAuthentication flow.
|
||||||
|
// If there is a need for additional context after registration,
|
||||||
|
// we could provide that method in the PostCreation trigger too,
|
||||||
|
// without breaking existing actions.
|
||||||
|
// Also, if that field is needed, we probably also should provide it
|
||||||
|
// for ExternalAuthentication.
|
||||||
|
user, metadatas, err := l.customUserToLoginUserMapping(r.Context(), authRequest, data.toHumanDomain(), make([]*domain.Metadata, 0), resourceOwner, domain.FlowTypeInternalAuthentication)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.renderRegister(w, r, authRequest, data, err)
|
l.renderRegister(w, r, authRequest, data, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
user, err = l.command.RegisterHuman(setContext(r.Context(), resourceOwner), resourceOwner, user, nil, nil, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator)
|
||||||
|
if err != nil {
|
||||||
|
l.renderRegister(w, r, authRequest, data, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(metadatas) > 0 {
|
||||||
|
_, err = l.command.BulkSetUserMetadata(r.Context(), user.AggregateID, resourceOwner, metadatas...)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: What if action is configured to be allowed to fail? Same question for external registration.
|
||||||
|
l.renderRegister(w, r, authRequest, data, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userGrants, err := l.customGrants(r.Context(), user.AggregateID, authRequest, resourceOwner, domain.FlowTypeInternalAuthentication)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = l.appendUserGrants(r.Context(), userGrants, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if authRequest == nil {
|
if authRequest == nil {
|
||||||
l.defaultRedirect(w, r)
|
l.defaultRedirect(w, r)
|
||||||
return
|
return
|
||||||
|
@@ -126,7 +126,8 @@ func (c *Commands) RemoveInstanceMember(ctx context.Context, userID string) (*do
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if errors.IsNotFound(err) {
|
if errors.IsNotFound(err) {
|
||||||
return nil, nil
|
// empty response because we have no data that match the request
|
||||||
|
return &domain.ObjectDetails{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
instanceAgg := InstanceAggregateFromWriteModel(&memberWriteModel.MemberWriteModel.WriteModel)
|
instanceAgg := InstanceAggregateFromWriteModel(&memberWriteModel.MemberWriteModel.WriteModel)
|
||||||
|
@@ -480,7 +480,7 @@ func TestCommandSide_RemoveIAMMember(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "member not existing, nil result",
|
name: "member not existing, empty object details result",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: eventstoreExpect(
|
||||||
t,
|
t,
|
||||||
@@ -492,7 +492,7 @@ func TestCommandSide_RemoveIAMMember(t *testing.T) {
|
|||||||
userID: "user1",
|
userID: "user1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
want: nil,
|
want: &domain.ObjectDetails{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -145,7 +145,8 @@ func (c *Commands) RemoveOrgMember(ctx context.Context, orgID, userID string) (*
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if errors.IsNotFound(err) {
|
if errors.IsNotFound(err) {
|
||||||
return nil, nil
|
// empty response because we have no data that match the request
|
||||||
|
return &domain.ObjectDetails{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
orgAgg := OrgAggregateFromWriteModel(&m.MemberWriteModel.WriteModel)
|
orgAgg := OrgAggregateFromWriteModel(&m.MemberWriteModel.WriteModel)
|
||||||
|
@@ -784,7 +784,7 @@ func TestCommandSide_RemoveOrgMember(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "member not existing, nil result",
|
name: "member not existing, empty object details result",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: eventstoreExpect(
|
||||||
t,
|
t,
|
||||||
@@ -798,7 +798,7 @@ func TestCommandSide_RemoveOrgMember(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
want: nil,
|
want: &domain.ObjectDetails{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -94,7 +94,8 @@ func (c *Commands) RemoveProjectMember(ctx context.Context, projectID, userID, r
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if errors.IsNotFound(err) {
|
if errors.IsNotFound(err) {
|
||||||
return nil, nil
|
// empty response because we have no data that match the request
|
||||||
|
return &domain.ObjectDetails{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
projectAgg := ProjectAggregateFromWriteModel(&m.MemberWriteModel.WriteModel)
|
projectAgg := ProjectAggregateFromWriteModel(&m.MemberWriteModel.WriteModel)
|
||||||
|
@@ -551,7 +551,7 @@ func TestCommandSide_RemoveProjectMember(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "member not existing, nil result",
|
name: "member not existing, empty object details result",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: eventstoreExpect(
|
||||||
t,
|
t,
|
||||||
@@ -565,7 +565,7 @@ func TestCommandSide_RemoveProjectMember(t *testing.T) {
|
|||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
want: nil,
|
want: &domain.ObjectDetails{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -20,6 +20,7 @@ const (
|
|||||||
FlowTypeUnspecified FlowType = iota
|
FlowTypeUnspecified FlowType = iota
|
||||||
FlowTypeExternalAuthentication
|
FlowTypeExternalAuthentication
|
||||||
FlowTypeCustomiseToken
|
FlowTypeCustomiseToken
|
||||||
|
FlowTypeInternalAuthentication
|
||||||
flowTypeCount
|
flowTypeCount
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,6 +50,12 @@ func (s FlowType) TriggerTypes() []TriggerType {
|
|||||||
TriggerTypePreUserinfoCreation,
|
TriggerTypePreUserinfoCreation,
|
||||||
TriggerTypePreAccessTokenCreation,
|
TriggerTypePreAccessTokenCreation,
|
||||||
}
|
}
|
||||||
|
case FlowTypeInternalAuthentication:
|
||||||
|
return []TriggerType{
|
||||||
|
TriggerTypePostAuthentication,
|
||||||
|
TriggerTypePreCreation,
|
||||||
|
TriggerTypePostCreation,
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -71,6 +78,8 @@ func (s FlowType) LocalizationKey() string {
|
|||||||
return "Action.Flow.Type.ExternalAuthentication"
|
return "Action.Flow.Type.ExternalAuthentication"
|
||||||
case FlowTypeCustomiseToken:
|
case FlowTypeCustomiseToken:
|
||||||
return "Action.Flow.Type.CustomiseToken"
|
return "Action.Flow.Type.CustomiseToken"
|
||||||
|
case FlowTypeInternalAuthentication:
|
||||||
|
return "Action.Flow.Type.InternalAuthentication"
|
||||||
default:
|
default:
|
||||||
return "Action.Flow.Type.Unspecified"
|
return "Action.Flow.Type.Unspecified"
|
||||||
}
|
}
|
||||||
|
@@ -42,7 +42,6 @@ func (a *AuthRequestOIDC) IsValid() bool {
|
|||||||
|
|
||||||
type AuthRequestSAML struct {
|
type AuthRequestSAML struct {
|
||||||
ID string
|
ID string
|
||||||
RequestID string
|
|
||||||
BindingType string
|
BindingType string
|
||||||
Code string
|
Code string
|
||||||
Issuer string
|
Issuer string
|
||||||
|
@@ -1117,6 +1117,7 @@ Action:
|
|||||||
Unspecified: Unspezifiziert
|
Unspecified: Unspezifiziert
|
||||||
ExternalAuthentication: Externe Authentifizierung
|
ExternalAuthentication: Externe Authentifizierung
|
||||||
CustomiseToken: Token ergänzen
|
CustomiseToken: Token ergänzen
|
||||||
|
InternalAuthentication: Interne Authentifizierung
|
||||||
TriggerType:
|
TriggerType:
|
||||||
Unspecified: Unspezifiziert
|
Unspecified: Unspezifiziert
|
||||||
PostAuthentication: Nach Authentifizierung
|
PostAuthentication: Nach Authentifizierung
|
||||||
|
@@ -1117,6 +1117,7 @@ Action:
|
|||||||
Unspecified: Unspecified
|
Unspecified: Unspecified
|
||||||
ExternalAuthentication: External Authentication
|
ExternalAuthentication: External Authentication
|
||||||
CustomiseToken: Complement Token
|
CustomiseToken: Complement Token
|
||||||
|
InternalAuthentication: Internal Authentication
|
||||||
TriggerType:
|
TriggerType:
|
||||||
Unspecified: Unspecified
|
Unspecified: Unspecified
|
||||||
PostAuthentication: Post Authentication
|
PostAuthentication: Post Authentication
|
||||||
|
@@ -946,6 +946,7 @@ Action:
|
|||||||
Unspecified: Non spécifié
|
Unspecified: Non spécifié
|
||||||
ExternalAuthentication: Authentification externe
|
ExternalAuthentication: Authentification externe
|
||||||
CustomiseToken: Compléter Token
|
CustomiseToken: Compléter Token
|
||||||
|
InternalAuthentication: Authentification interne
|
||||||
TriggerType:
|
TriggerType:
|
||||||
Unspecified: Non spécifié
|
Unspecified: Non spécifié
|
||||||
PostAuthentication: Authentification postérieure
|
PostAuthentication: Authentification postérieure
|
||||||
|
@@ -946,6 +946,7 @@ Action:
|
|||||||
Unspecified: Non specificato
|
Unspecified: Non specificato
|
||||||
ExternalAuthentication: Autenticazione esterna
|
ExternalAuthentication: Autenticazione esterna
|
||||||
CustomiseToken: Completare Token
|
CustomiseToken: Completare Token
|
||||||
|
InternalAuthentication: Autenticazione interna
|
||||||
TriggerType:
|
TriggerType:
|
||||||
Unspecified: Non specificato
|
Unspecified: Non specificato
|
||||||
PostAuthentication: Post-autenticazione
|
PostAuthentication: Post-autenticazione
|
||||||
|
@@ -936,6 +936,7 @@ Action:
|
|||||||
Unspecified: 未指定的
|
Unspecified: 未指定的
|
||||||
ExternalAuthentication: 外部认证
|
ExternalAuthentication: 外部认证
|
||||||
CustomiseToken: 自定义令牌
|
CustomiseToken: 自定义令牌
|
||||||
|
InternalAuthentication: 内部认证
|
||||||
TriggerType:
|
TriggerType:
|
||||||
Unspecified: 未指定的
|
Unspecified: 未指定的
|
||||||
PostAuthentication: 后期认证
|
PostAuthentication: 后期认证
|
||||||
|
Reference in New Issue
Block a user