-
Notifications
You must be signed in to change notification settings - Fork 79
Seed POC: passwordless and MFA #2454
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
ShadowCat567
wants to merge
8
commits into
aws-amplify:main
Choose a base branch
from
ShadowCat567:passwordless-auth
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
17b1183
passwordless part 1 works - email/text
380e340
mfa with TOTP works
b207a63
split sign up flows
c770cfe
sign in
6233a7a
changeset
f933936
making api checks happy
4b19301
api stuff
f9c59b9
testing authenticator app set up
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
'@aws-amplify/seed': minor | ||
'@aws-amplify/backend-cli': minor | ||
--- | ||
|
||
added seed command and seed package |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import { Argv, CommandModule } from 'yargs'; | ||
import path from 'path'; | ||
import { existsSync } from 'fs'; | ||
import { execa } from 'execa'; | ||
import { SandboxBackendIdResolver } from './sandbox_id_resolver.js'; | ||
import { PackageJsonReader } from '@aws-amplify/platform-core'; | ||
import { LocalNamespaceResolver } from '../../backend-identifier/local_namespace_resolver.js'; | ||
|
||
/** | ||
* | ||
*/ | ||
export class SandboxSeedCommand implements CommandModule<object> { | ||
/** | ||
* @inheritDoc | ||
*/ | ||
readonly command: string; | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
readonly describe: string; | ||
|
||
/** | ||
* Seeds sandbox environment. | ||
*/ | ||
constructor() { | ||
this.command = 'seed'; | ||
this.describe = 'Seeds sandbox environment'; | ||
} | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
handler = async (): Promise<void> => { | ||
const sandboxID = await new SandboxBackendIdResolver( | ||
new LocalNamespaceResolver(new PackageJsonReader()) | ||
).resolve(); | ||
|
||
//most of this comes from the initial POC for seed, changed filepath to be more inline with discussions that have happened since then | ||
const seedPath = path.join('seed.ts'); | ||
await execa('tsx', [seedPath], { | ||
cwd: process.cwd(), | ||
stdio: 'inherit', | ||
env: { | ||
AMPLIFY_SANDBOX_IDENTIFIER: JSON.stringify(sandboxID), | ||
}, | ||
}); | ||
}; | ||
|
||
/** | ||
* @inheritDoc | ||
*/ | ||
//this section also comes from the initial POC for seed | ||
builder = (yargs: Argv) => { | ||
return yargs.check(() => { | ||
//seed path may need to be more flexible or be in a different place | ||
const seedPath = path.join(process.cwd(), 'seed.ts'); | ||
if (!existsSync(seedPath)) { | ||
throw new Error(`${seedPath} must exist`); | ||
} | ||
return true; | ||
}); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# Be very careful editing this file. It is crafted to work around [this issue](https://github.com/npm/npm/issues/4479) | ||
|
||
# First ignore everything | ||
**/* | ||
|
||
# Then add back in transpiled js and ts declaration files | ||
!lib/**/*.js | ||
!lib/**/*.d.ts | ||
|
||
# Then ignore test js and ts declaration files | ||
*.test.js | ||
*.test.d.ts | ||
|
||
# This leaves us with including only js and ts declaration files of functional code |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
## API Report File for "@aws-amplify/seed" | ||
|
||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). | ||
|
||
```ts | ||
|
||
// @public (undocumented) | ||
export type AuthClient = { | ||
createUser: (newUser: AuthUser) => Promise<AuthUser>; | ||
signInUser: (userToSignIn: AuthUser) => Promise<void>; | ||
}; | ||
|
||
// @public (undocumented) | ||
export type AuthUser = { | ||
username: string; | ||
signUpOption: 'MFA' | 'Passwordless'; | ||
password?: string; | ||
}; | ||
|
||
// @public | ||
export const getAuthClient: (outputs: any) => AuthClient; | ||
|
||
// (No @packageDocumentation comment for this package) | ||
|
||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Description | ||
|
||
Replace with a description of this package |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": "../../api-extractor.base.json" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
{ | ||
"name": "@aws-amplify/seed", | ||
"version": "0.1.0", | ||
"type": "module", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"exports": { | ||
".": { | ||
"types": "./lib/index.d.ts", | ||
"import": "./lib/index.js", | ||
"require": "./lib/index.js" | ||
} | ||
}, | ||
"main": "lib/index.js", | ||
"types": "lib/index.d.ts", | ||
"scripts": { | ||
"update:api": "api-extractor run --local" | ||
}, | ||
"license": "Apache-2.0", | ||
"dependencies": { | ||
"@aws-amplify/cli-core": "^1.2.3", | ||
"@aws-amplify/client-config": "^1.5.5", | ||
"@aws-sdk/client-cognito-identity-provider": "^3.734.0", | ||
"aws-amplify": "^6.12.1" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
/** | ||
* Example usage: | ||
* const myLock = new AsyncLock(); | ||
* | ||
* async function asyncFunction() { | ||
* await myLock.acquire(); | ||
* try { | ||
* // Code that requires exclusive access to a shared resource | ||
* console.log('Accessing shared resource...'); | ||
* await someAsyncOperation(); | ||
* } finally { | ||
* myLock.release(); | ||
* } | ||
* } | ||
* | ||
* asyncFunction(); | ||
*/ | ||
// took this from initial Seed POC, for the purposes of creating a new user | ||
// TODO this is a copy for the POC purposes. | ||
export class AsyncLock { | ||
private isLocked: boolean; | ||
private readonly queue: Array<(value?: never) => void>; | ||
|
||
/** | ||
* Creates async lock. | ||
*/ | ||
constructor(private readonly defaultTimeoutMs?: number) { | ||
this.isLocked = false; | ||
this.queue = []; | ||
} | ||
|
||
acquire = async (timeoutMs?: number): Promise<void> => { | ||
const lockPromise = new Promise<void>((resolve) => { | ||
if (!this.isLocked) { | ||
this.isLocked = true; | ||
resolve(); | ||
} else { | ||
this.queue.push(resolve); | ||
} | ||
}); | ||
timeoutMs = timeoutMs ?? this.defaultTimeoutMs; | ||
if (timeoutMs) { | ||
const timeoutPromise = new Promise<void>((resolve, reject) => | ||
setTimeout( | ||
() => | ||
reject( | ||
new Error(`Unable to acquire async lock in ${timeoutMs}ms.`) | ||
), | ||
timeoutMs | ||
) | ||
); | ||
return Promise.race<void>([lockPromise, timeoutPromise]); | ||
} | ||
return lockPromise; | ||
}; | ||
|
||
release = (): void => { | ||
const resolve = this.queue.shift(); | ||
if (resolve) { | ||
resolve(); | ||
} else { | ||
this.isLocked = false; | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
/* eslint-disable */ | ||
import { AsyncLock } from './async_lock.js'; | ||
import { AuthClient, AuthUser } from './types.js'; | ||
import { | ||
AdminCreateUserCommand, | ||
AdminInitiateAuthCommand, | ||
CognitoIdentityProviderClient, | ||
RespondToAuthChallengeCommand, | ||
} from '@aws-sdk/client-cognito-identity-provider'; | ||
import { randomUUID, sign } from 'node:crypto'; | ||
import { ClientConfigVersionTemplateType } from '@aws-amplify/client-config'; | ||
import * as auth from 'aws-amplify/auth'; | ||
//import assert from 'assert'; | ||
import { mfaSignUp } from './mfa_authentication.js'; | ||
import { passwordlessSignUp } from './passwordless_authentication.js'; | ||
import { AmplifyPrompter } from '@aws-amplify/cli-core'; | ||
|
||
// Took the skeleton of this from the initial Seed POC | ||
export class DefaultAuthClient implements AuthClient { | ||
/** | ||
* Asynchronous lock is used to assure that all calls to Amplify JS library are | ||
* made in single transaction. This is because that library maintains global state, | ||
* for example auth session. | ||
*/ | ||
// TODO setting timeout makes node process delay exit until that promise resolves, it should be handled somehow. | ||
private readonly lock: AsyncLock = new AsyncLock(); | ||
|
||
private readonly userPoolId: string; | ||
private readonly userPoolClientId: string; | ||
private readonly identityPoolId: string; | ||
private readonly allowGuestAccess: boolean | undefined; | ||
|
||
/** | ||
* Creates Amplify Auth client. | ||
*/ | ||
constructor( | ||
private readonly cognitoIdentityProviderClient: CognitoIdentityProviderClient, | ||
authConfig: NonNullable<ClientConfigVersionTemplateType<'1'>['auth']> | ||
) { | ||
if (!authConfig.identity_pool_id) { | ||
throw new Error('Client config must have identity pool id.'); | ||
} | ||
this.userPoolId = authConfig.user_pool_id; | ||
this.userPoolClientId = authConfig.user_pool_client_id; | ||
this.identityPoolId = authConfig.identity_pool_id; | ||
this.allowGuestAccess = authConfig.unauthenticated_identities_enabled; | ||
} | ||
|
||
// https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/sign-in/#sign-in-with-passwordless-methods | ||
createUser = async (user: AuthUser): Promise<AuthUser> => { | ||
await this.lock.acquire(); | ||
try { | ||
console.log(`creating ${user.username}`); | ||
|
||
// in case there's already signed user in the session. | ||
await auth.signOut(); | ||
|
||
switch (user.signUpOption) { | ||
case 'Passwordless': | ||
await this.cognitoIdentityProviderClient.send( | ||
new AdminCreateUserCommand({ | ||
Username: user.username, | ||
UserPoolId: this.userPoolId, | ||
MessageAction: 'SUPPRESS', | ||
}) | ||
); | ||
|
||
await passwordlessSignUp(user.username); | ||
console.log('Sign in successful'); | ||
break; | ||
case 'MFA': | ||
const temporaryPassword = `Test1@Temp${randomUUID().toString()}`; | ||
await this.cognitoIdentityProviderClient.send( | ||
new AdminCreateUserCommand({ | ||
Username: user.username, | ||
TemporaryPassword: temporaryPassword, | ||
UserPoolId: this.userPoolId, | ||
MessageAction: 'SUPPRESS', | ||
}) | ||
); | ||
|
||
await mfaSignUp(user.username, temporaryPassword, user.password!); | ||
console.log('Sign in successful'); | ||
break; | ||
} | ||
return user; | ||
} finally { | ||
try { | ||
// sign out to leave ok state; | ||
await auth.signOut(); | ||
} catch (e) { | ||
// eat it | ||
} | ||
console.log(`user ${user.username} created`); | ||
this.lock.release(); | ||
} | ||
}; | ||
|
||
//this works, but I would like for it to not require a challenge everytime someone wants to sign in with a user | ||
signInUser = async (user: AuthUser) => { | ||
switch (user.signUpOption) { | ||
case 'Passwordless': | ||
const signIn = await this.cognitoIdentityProviderClient.send( | ||
new AdminInitiateAuthCommand({ | ||
AuthFlow: 'USER_AUTH', | ||
ClientId: this.userPoolClientId, | ||
UserPoolId: this.userPoolId, | ||
AuthParameters: { | ||
USERNAME: user.username, | ||
}, | ||
}) | ||
); | ||
|
||
const challengeResponse = await AmplifyPrompter.input({ | ||
message: `Input a challenge response for ${user.username}: `, | ||
}); | ||
const output = await this.cognitoIdentityProviderClient.send( | ||
new RespondToAuthChallengeCommand({ | ||
ChallengeName: 'EMAIL_OTP', | ||
ChallengeResponses: { | ||
USERNAME: user.username, | ||
EMAIL_OTP_CODE: challengeResponse, | ||
}, | ||
ClientId: this.userPoolClientId, | ||
Session: signIn.Session, | ||
}) | ||
); | ||
console.log(output.AuthenticationResult); | ||
console.log('Signed in'); | ||
break; | ||
case 'MFA': | ||
const signInResult = await auth.signIn({ | ||
username: user.username, | ||
password: user.password, | ||
}); | ||
console.log(signInResult.nextStep.signInStep); | ||
|
||
if ( | ||
signInResult.nextStep.signInStep === 'CONFIRM_SIGN_IN_WITH_TOTP_CODE' | ||
) { | ||
const challengeResponse = await AmplifyPrompter.input({ | ||
message: `Input a challenge response for ${user.username}: `, | ||
}); | ||
const totp = await auth.confirmSignIn({ | ||
challengeResponse: challengeResponse, | ||
}); | ||
console.log(totp); | ||
} | ||
break; | ||
} | ||
}; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Check notice
Code scanning / CodeQL
Unused variable, import, function or class Note