Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/services/cognito/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2752,6 +2752,18 @@ try {
Cognito. Neither fires for `REFRESH_TOKEN_AUTH`, as neither does on real Cognito, where
`PreTokenGeneration` does.

A sign-in at the hosted domain runs `PreAuthentication` too, under the same
`PreAuthentication_Authentication` source real Cognito reports it from `/login`. It runs for a
password and for a passkey, once per sign-in, before the password is checked. A handler that throws
refuses the sign-in, and a served domain draws that refusal on the sign-in form the way it draws a
wrong password. A browser coming back on the managed login session it already holds runs nothing,
because the `PreAuthentication` docs say the trigger does not activate on the renewal of a session
that already exists.

`PostAuthentication` stays unfired at the hosted domain. AWS names it for a federated sign-in and
leaves it out of the table for a local user at managed login, and what real Cognito does there was
not checked against a live account.

### Federated sign-in triggers

A user arriving from an identity provider runs the pool's triggers too, and which ones it runs
Expand Down Expand Up @@ -4672,6 +4684,8 @@ Sim Cognito currently supports:
- The triggers a federated sign-in runs, being `PreSignUp` and `PostConfirmation` on a first
sign-in, `PreAuthentication` and `PostAuthentication` on every one after it, and
`PreTokenGeneration` under `TokenGeneration_HostedAuth` when the code is exchanged
- The `PreAuthentication` trigger a local user's managed login sign-in runs, for a password and for
a passkey alike, with a handler's refusal drawn on the sign-in form
- A `REGIONAL` web ACL in front of the pool, attached by `AssociateWebACL` on simulated WAFv2 and
evaluated against every request the hosted domain and the two `.well-known` documents answer
- App client OAuth settings: `AllowedOAuthFlowsUserPoolClient`, `AllowedOAuthFlows`,
Expand Down Expand Up @@ -4931,6 +4945,11 @@ Current documented limitations:
`EXTERNAL_PROVIDER` and never passes through `UNCONFIRMED`, so there is no confirmation for a
handler to skip. `autoVerifyEmail` and `autoVerifyPhone` are applied there as they are for a
sign-up.
- A local user signing in at managed login runs `PreAuthentication` and leaves `PostAuthentication`
unfired. The documented table names the first for `/login` and the second only for a federated
sign-in, and what real Cognito does with the second there was not checked against a live account.
- A browser signed in from the managed login session it was already holding runs no sign-in trigger,
which is what the `PreAuthentication` docs say of a renewed session.
- A code exchanged at the token endpoint reports `TokenGeneration_HostedAuth` where the sign-in
happened at an identity provider, and `TokenGeneration_Authentication` where the pool signed in
one of its own users. Real Cognito reports the same two. A browser signed in from the managed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export class SimCognitoHostedCommands {
}),
challenge,
clock,
triggers,
}),
clock,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
requireSimCognitoReadyToSignIn,
requireSimCognitoSignInUser,
} from "../../user-pool/auth/sim-cognito-sign-in.js";
import type { SimCognitoUserPoolTriggers } from "../../user-pool/trigger/sim-cognito-user-pool-triggers.js";
import type { SimCognitoUserPoolClient } from "../../user-pool/client/sim-cognito-user-pool-client.js";
import type { SimCognitoUserPool } from "../../user-pool/sim-cognito-user-pool.js";
import type { SimCognitoUser } from "../../user-pool/user/sim-cognito-user.js";
Expand Down Expand Up @@ -33,6 +34,7 @@ interface SimCognitoHostedPasskeySignInProperties {
*/
readonly challenge: SimCognitoFirstFactorChallenge;
readonly clock: SimClock;
readonly triggers: SimCognitoUserPoolTriggers;
}

/**
Expand All @@ -58,22 +60,36 @@ interface SimCognitoHostedPasskeySignInProperties {
export class SimCognitoHostedPasskeySignIn {
private readonly challenge: SimCognitoFirstFactorChallenge;
private readonly clock: SimClock;
private readonly triggers: SimCognitoUserPoolTriggers;

constructor(properties: SimCognitoHostedPasskeySignInProperties) {
this.challenge = properties.challenge;
this.clock = properties.clock;
this.triggers = properties.triggers;
}

/**
* Ask this user for a passkey, or refuse where the pool allows none and
* where the user has registered none.
*
* `PreAuthentication` runs here rather than where the credential comes back,
* because this is the request that starts the sign-in. A `USER_AUTH`
* sign-in runs it in the same place, on the `InitiateAuth` that asks for a
* factor, and the challenge response that answers runs it no second time.
* Only a request that has been asked holds a session to answer with, so
* every passkey sign-in passes through here exactly once.
*/
ask(
async ask(
pool: SimCognitoUserPool,
client: SimCognitoUserPoolClient,
username: string,
): never {
const user = this.signingIn(pool, client, username);
): Promise<never> {
const user = requireSimCognitoSignInUser(pool, client, username);

await this.triggers.preAuthentication({ pool, client, user });

requireSimCognitoReadyToSignIn(user);

const asked = this.challenge.issue(
{ pool, client, user },
simCognitoWebAuthnChallenge,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from "../../user-pool/auth/sim-cognito-sign-in.js";
import type { SimCognitoUserPoolClient } from "../../user-pool/client/sim-cognito-user-pool-client.js";
import type { SimCognitoUserPool } from "../../user-pool/sim-cognito-user-pool.js";
import type { SimCognitoUserPoolTriggers } from "../../user-pool/trigger/sim-cognito-user-pool-triggers.js";
import type { SimCognitoUser } from "../../user-pool/user/sim-cognito-user.js";
import { simCognitoChallengeFactor } from "../auth/sim-cognito-mfa-factor-choice.js";
import type { SimCognitoHostedCredentials } from "./sim-cognito-hosted-credentials.js";
Expand All @@ -26,17 +27,30 @@ import type { SimCognitoHostedCredentials } from "./sim-cognito-hosted-credentia
* reach one is refused here with a message saying which.
*/
export class SimCognitoHostedPasswordSignIn {
private readonly triggers: SimCognitoUserPoolTriggers;

constructor(properties: { readonly triggers: SimCognitoUserPoolTriggers }) {
this.triggers = properties.triggers;
}

/**
* The user these credentials sign in, having checked the password.
*
* `PreAuthentication` runs once the user is known and before the password is
* checked, which is where the API sign-ins run it and where real managed
* login reports it from `/login`. A wrong password reaches the handler too,
* because the trigger is given the user to decide about.
*/
signIn(
async signIn(
pool: SimCognitoUserPool,
client: SimCognitoUserPoolClient,
credentials: SimCognitoHostedCredentials,
): SimCognitoUser {
): Promise<SimCognitoUser> {
const { username, password } = credentials;
const user = requireSimCognitoSignInUser(pool, client, username);

await this.triggers.preAuthentication({ pool, client, user });

requireSimCognitoSignIn(user, password);
requireSimCognitoConfirmed(user);
requireSimCognitoPasswordSet(user);
Expand Down
21 changes: 16 additions & 5 deletions src/service/cognito/command/hosted/sim-cognito-hosted-sign-in.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SimCognitoManagedLoginRequired } from "../../error/sim-cognito-managed-
import type { SimCognitoUserPoolClient } from "../../user-pool/client/sim-cognito-user-pool-client.js";
import type { SimCognitoFederatedSignIn } from "../../user-pool/idp/sim-cognito-federated-sign-in.js";
import type { SimCognitoUserPool } from "../../user-pool/sim-cognito-user-pool.js";
import type { SimCognitoUserPoolTriggers } from "../../user-pool/trigger/sim-cognito-user-pool-triggers.js";
import type { SimCognitoFirstFactorChallenge } from "../auth/sim-cognito-first-factor-challenge.js";
import { SimCognitoAuthorizeRequest } from "./sim-cognito-authorize-request.js";
import { SimCognitoBrowserSession } from "./sim-cognito-browser-session.js";
Expand All @@ -21,6 +22,12 @@ interface SimCognitoHostedSignInProperties {
*/
readonly challenge: SimCognitoFirstFactorChallenge;
readonly clock: SimClock;

/**
* The trigger runner the API sign-ins use, which a sign-in at this endpoint
* runs the pool's `PreAuthentication` through.
*/
readonly triggers: SimCognitoUserPoolTriggers;
}

/**
Expand All @@ -37,16 +44,20 @@ export class SimCognitoHostedSignIn {
private readonly federatedSignIn: SimCognitoFederatedSignIn;
private readonly clock: SimClock;
private readonly request = new SimCognitoAuthorizeRequest();
private readonly passwordSignIn = new SimCognitoHostedPasswordSignIn();
private readonly passwordSignIn: SimCognitoHostedPasswordSignIn;
private readonly passkeySignIn: SimCognitoHostedPasskeySignIn;
private readonly browserSession = new SimCognitoBrowserSession();

constructor(properties: SimCognitoHostedSignInProperties) {
this.federatedSignIn = properties.federatedSignIn;
this.clock = properties.clock;
this.passwordSignIn = new SimCognitoHostedPasswordSignIn({
triggers: properties.triggers,
});
this.passkeySignIn = new SimCognitoHostedPasskeySignIn({
challenge: properties.challenge,
clock: properties.clock,
triggers: properties.triggers,
});
}

Expand Down Expand Up @@ -89,12 +100,12 @@ export class SimCognitoHostedSignIn {
* the sign-in form is shown for. The serving layer answers with that page,
* from this refusal.
*/
private localSignIn(
private async localSignIn(
pool: SimCognitoUserPool,
client: SimCognitoUserPoolClient,
input: SimCognitoAuthorizeInput,
presentedSession: string | undefined,
): SimCognitoHostedSignedIn {
): Promise<SimCognitoHostedSignedIn> {
const now = this.clock.now();

const { username, credential } = input;
Expand All @@ -112,7 +123,7 @@ export class SimCognitoHostedSignIn {
}

if (username !== undefined && input.passkey !== undefined) {
this.passkeySignIn.ask(pool, client, username);
await this.passkeySignIn.ask(pool, client, username);
}

const credentials = SimCognitoHostedCredentials.in(input);
Expand All @@ -127,7 +138,7 @@ export class SimCognitoHostedSignIn {
return returning;
}

const user = this.passwordSignIn.signIn(pool, client, credentials);
const user = await this.passwordSignIn.signIn(pool, client, credentials);

return this.browserSession.start(pool, user, now);
}
Expand Down
Loading