Open12

Amplify Gen2 React TypeScript auth function (amplify-qrticket)

marchanmarchan
  • 2024.06.07

React TypeScriptプロジェクトを最新版の Vite で作成

$ npm create vite@latest amplify-qrticket -- --template react-ts   

Need to install the following packages:
create-vite@5.2.3

Ok to proceed? (y) y

Scaffolding project in /home/ec2-user/environment/amplify-qrticket...

Done. Now run:

cd amplify-qrticket
npm install
npm run dev

$ cd amplify-qrticket/
$ ls -ls

total 24
4 -rw-r--r--. 1 ec2-user ec2-user 1300 Jun 6 04:49 README.md
4 -rw-r--r--. 1 ec2-user ec2-user 366 Jun 6 04:49 index.html
4 -rw-r--r--. 1 ec2-user ec2-user 751 Jun 6 04:49 package.json
0 drwxr-xr-x. 2 ec2-user ec2-user 22 Jun 6 04:49 public
0 drwxr-xr-x. 3 ec2-user ec2-user 104 Jun 6 04:49 src
4 -rw-r--r--. 1 ec2-user ec2-user 605 Jun 6 04:49 tsconfig.json
4 -rw-r--r--. 1 ec2-user ec2-user 233 Jun 6 04:49 tsconfig.node.json
4 -rw-r--r--. 1 ec2-user ec2-user 163 Jun 6 04:49 vite.config.ts

marchanmarchan

依存関係をインストールする

$ npm install

npm WARN deprecated inflight@1.0.6: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
npm WARN deprecated rimraf@3.0.2: Rimraf versions prior to v4 are no longer supported
npm WARN deprecated glob@7.2.3: Glob versions prior to v9 are no longer supported

added 214 packages, and audited 215 packages in 14s

41 packages are looking for funding
run npm fund for details

found 0 vulnerabilities

開発サーバーを起動する

$ npm run dev

VITE v5.2.12 ready in 505 ms

➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help

marchanmarchan

vite.config.ts の内容を変更

vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
  // cloud9が許可するポート番号
  server: { ●追加
    port: 8080, ●追加
  },   ●追加
  plugins: [react()],
})

開発サーバーを起動する

$ npm run dev

amplify-qrticket@0.0.0 dev
vite

VITE v5.2.12 ready in 505 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help
5:05:49 AM [vite] vite.config.ts changed, restarting server...
5:05:49 AM [vite] server restarted.
➜ Local: http://localhost:8080/
➜ Network: use --host to expose

marchanmarchan
  • 2024.06.06(木) 作業メモ

@aws-amplify/backend をインストール

npm add --save-dev @aws-amplify/backend@latest

npm WARN deprecated @babel/plugin-proposal-class-properties@7.18.6: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.
npm WARN deprecated @babel/plugin-proposal-object-rest-spread@7.20.7: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.
npm WARN deprecated core-js@2.6.12: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.

added 439 packages, and audited 772 packages in 59s

119 packages are looking for funding
run npm fund for details

found 0 vulnerabilities

marchanmarchan

@aws-amplify/backend-cli をインストール

npm add --save-dev @aws-amplify/backend-cli@latest

added 440 packages, and audited 1212 packages in 35s

163 packages are looking for funding
run npm fund for details

found 0 vulnerabilities

marchanmarchan

backend を作成

~/environment/amplify-qrticket $ mkdir amplify
~/environment/amplify-qrticket $ touch amplify/backend.ts
  • backend.ts を新規に作成する。(設定内容はなし)
import { defineBackend } from '@aws-amplify/backend';

defineBackend({});
marchanmarchan

sandboxを作成

  • npm run devを実行中のターミナルと別のターミナルを開き、以下のコマンドを実行
~/environment/amplify-qrticket $ npx ampx sandbox

[Sandbox] Pattern !.vscode/extensions.json found in .gitignore. ".vscode/extensions.json" will not be watched if other patterns in .gitignore are excluding it.

Amplify Sandbox

Identifier: ec2-user
Stack: amplify-amplifyqrticket-ec2user-sandbox-7052f44ddd

To specify a different sandbox identifier, use --identifier

✨ Synthesis time: 0.06s

⚠️ The --hotswap and --hotswap-fallback flags deliberately introduce CloudFormation drift to speed up deployments
⚠️ They should only be used for development - never use them for your production Stacks!

●省略

⚠️ The following non-hotswappable changes were found:
logicalID: deploymentType, type: Stack Output, reason: output was changed
logicalID: region, type: Stack Output, reason: output was changed
logicalID: CDKMetadata, type: AWS::CDK::Metadata, reason: resource 'CDKMetadata' was created by this deployment

●省略

✅ amplify-amplifyqrticket-ec2user-sandbox-7052f44ddd

✨ Deployment time: 10.94s

Outputs:
amplify-amplifyqrticket-ec2user-sandbox-7052f44ddd.deploymentType = sandbox
amplify-amplifyqrticket-ec2user-sandbox-7052f44ddd.region = ap-northeast-1
Stack ARN:
arn:aws:cloudformation:ap-northeast-1:053423220213:stack/amplify-amplifyqrticket-ec2user-sandbox-7052f44ddd/717d2b60-23c6-11ef-8107-0e3d6a90d7dd

✨ Total time: 11s

[Sandbox] Watching for file changes...
File written: amplify_outputs.json

  • このブラウザタブが動作している限り、sandbox が起動している。
  • 以下 別のターミナルで作業する。
~/environment/amplify-qrticket $ cat amplify_outputs.json

{
"version": "1"
}

marchanmarchan

backend に認証機能(Cognito)を追加

  • ディレクトリ、ファイルを新規に作成する
~/environment/amplify-qrticket/amplify $ mkdir auth
~/environment/amplify-qrticket/amplify $ touch auth/resource.ts
  • amplify/auth/resource.tsを新規に作成する
amplify/auth/resource.ts
import { defineAuth } from '@aws-amplify/backend';
export const auth = defineAuth({
    loginWith: {
        email: true
    }
});
  • amplify/backend.tsにauthを追加する
amplify/backend.ts
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource'; ●追加

defineBackend({});●削除
defineBackend({●追加
     auth,●追加
});●追加
  • Credential 期限切れで反映されず
  • Cloud9 の Credentialを 一時的なものから IAMユーザのものに変更して正常化
marchanmarchan

frontend に認証機能(Cognito)を追加

  • @aws-amplify/ui-reactをインストールする
npm add @aws-amplify/ui-react

  • src/main.tsxを編集する(Amplifyの設定を読み込む)
src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
  
import '@aws-amplify/ui-react/styles.css' ●追加

import { Amplify } from 'aws-amplify' ●追加
import outputs from '../amplify_outputs.json' ●追加
Amplify.configure(outputs) ●追加
  
ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
      <App />
  </React.StrictMode>,
)

  • src/App.tsxを編集する(Authenticatorコンポーネントを追加)
src/App.tsx
  import { useState } from 'react'
  import reactLogo from './assets/react.svg'
  import viteLogo from '/vite.svg'
  import './App.css'
+ import { Authenticator } from '@aws-amplify/ui-react'●追加
  
  function App() {
    const [count, setCount] = useState(0)
  
    return (
+     <Authenticator>●追加
+       {({ signOut, user }) => (●追加
      <>
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <p>
          Edit <code>src/App.tsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
    </>
+       )}●追加
+     </Authenticator> ●追加
    )
  }
  export default App

  • 開発サーバでアプリケーションを起動する。
$ npm run dev

amplify-qrticket@0.0.0 dev
vite
VITE v5.2.12 ready in 650 ms
➜ Local: http://localhost:8080/
➜ Network: use --host to expose
➜ press h + enter to show help4:25:45 AM [vite] ✨ new dependencies optimized: @aws-amplify/ui-react
4:25:45 AM [vite] ✨ optimized dependencies changed. reloading


2024.06.12(木)

  • sandboxを起動する。
$ npx ampx sandbox
  • Preview Runnning Application をクリックして開発サーバでプレビューする。

  • 認証画面でメールアドレスとパスワードを入力してアカウントを新規作成する。

  • 受信メールに記載された認証コード(数字)を入力する。

  • ログイン画面が表示される。

  • management console dognito で ユーザプールを確認する。

marchanmarchan
  • 2024.06.12(水)

function (Lambda関数)をTypeScriptで追加

  • amplify sandboxを起動する。
$ npx ampx sandbox

  • aws-lambda ライブラリをインストールする。
$ npm add --save-dev aws-lambda

up to date, audited 1529 packages in 7s
170 packages are looking for funding
run npm fund for details
found 0 vulnerabilities


  • Lambda関数 amplify/function/hello/resource.tsを作成する
amplify/function/hello/resource.ts
import { defineFunction } from '@aws-amplify/backend';

export const hello = defineFunction({
    name: 'hello',
    entry: './handler.ts',
})

  • Lambda関数 amplify/function/hello-world/handler.tsを作成する。
amplify/function/hello/handler.ts
import { Handler } from 'aws-lambda';

export const handler: Handler = async (event, context) => {
    return { message: 'Hello, World' }
}

  • backend.ts に function を追加する
amplify/backend.ts
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { hello } from './function/hello/resource' ●追加

const backend = defineBackend({ ●修正
  auth,
  hello, ●追加
});

認証ユーザにLambda 関数の実行権限を付与


  • 認証ユーザに作成したLambda関数の実行権限を付与する。
amplify/backend.ts
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { hello } from './function/hello/resource'

const backend = defineBackend({
  auth,
  hello,
});

//認証されたユーザーのIAMロールの取得 ●追加
const authenticatedUserIamRole = backend.auth.resources.authenticatedUserIamRole;

//Lambda関数への実行権限の付与 ●追加
backend.hello.resources.lambda.grantInvoke(authenticatedUserIamRole);

amplify_outputs.jsonにLambda関数名を出力

  • amplify_outputs.jsonにLambda関数名を出力する。
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { hello } from './function/hello/resource'
const backend = defineBackend({
  auth,
  hello,
});
//認証されたユーザーのIAMロールの取得
const authenticatedUserIamRole = backend.auth.resources.authenticatedUserIamRole;
//Lambda関数への実行権限の付与
backend.hello.resources.lambda.grantInvoke(authenticatedUserIamRole);
//
backend.addOutput({
  custom: {
    helloFunctionName: backend.hello.resources.lambda.functionName,
  },
});
  • amplify_outputs.json の出力を確認する。
{
  "auth": {
    "user_pool_id": "ap-northeast-1_L**",
    "aws_region": "ap-northeast-1",
    "user_pool_client_id": "36senjs1893d0mmgkj***",
    "identity_pool_id": "ap-northeast-1:48d676ec-e3e8-4d91-95a6-***",
    "standard_required_attributes": [
      "email"
    ],
    "username_attributes": [
      "email"
    ],
    "user_verification_types": [
      "email"
    ],
    "password_policy": {
      "min_length": 8,
      "require_numbers": true,
      "require_lowercase": true,
      "require_uppercase": true,
      "require_symbols": true
    },
    "unauthenticated_identities_enabled": true
  },
  "version": "1",
  "custom": {
    "helloFunctionName": "amplify-amplifyqrticket-ec2use-hel***" ●確認した
  }
}

frontend src/app.tsxを変更する

import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'

import { Authenticator } from '@aws-amplify/ui-react'

import { InvokeCommand, LambdaClient } from '@aws-sdk/client-lambda' ●追加
import { fetchAuthSession } from 'aws-amplify/auth' ●追加
  
import outputs from "../amplify_outputs.json" ●追加

function App() {
  const [count, setCount] = useState(0)
 ●追加 ここから
  const [text, setText] = useState("")
  
  async function invokeHelloWorld() {

    const { credentials } = await fetchAuthSession()
    const awsRegion = outputs.auth.aws_region
    const functionName = outputs.custom.helloFunctionName

    const labmda = new LambdaClient({ credentials: credentials, region: awsRegion })
    const command = new InvokeCommand({
      FunctionName: functionName,
    });
    const apiResponse = await labmda.send(command);

    if (apiResponse.Payload) {
      const payload = JSON.parse(new TextDecoder().decode(apiResponse.Payload))
      setText(payload.message)
    }
  }
●追加 ここまで
  return (
    <Authenticator>
      {({ signOut, user }) => (
    <>
      <div>
        <a href="https://vitejs.dev" target="_blank">
          <img src={viteLogo} className="logo" alt="Vite logo" />
        </a>
        <a href="https://react.dev" target="_blank">
          <img src={reactLogo} className="logo react" alt="React logo" />
        </a>
      </div>
      <h1>Vite + React</h1>
      <div className="card">
        <button onClick={() => setCount((count) => count + 1)}>
          count is {count}
        </button>
        <p>
          Edit <code>src/App.tsx</code> and save to test HMR
        </p>
      </div>
      <p className="read-the-docs">
        Click on the Vite and React logos to learn more
      </p>
      <p>●追加
        <button onClick={invokeHello}>invokeHello</button>●追加
        <div>{text}</div>●追加
      </p>●追加
    </>
        )}
    </Authenticator>
  )
}
export default App

  • ブラウザでプレビューし、ボタンをクリックすると Lambda 関数が呼び出され結果が表示される。
marchanmarchan
  • 2024.06.13(木)
  • Cloud9の環境を確認する。
$ node --version
v18.18.2
$ npm --version
9.8.1
$ aws --version
aws-cli/2.15.10 Python/3.11.6 Linux/6.1.66-93.164.amzn2023.x86_64 exe/x86_64.amzn.2023 prompt/off
  • sandboxを起動する。
$ npx ampx sandbox
  • 開発サーバを起動する。
$ npm run dev

amplify-qrticket@0.0.0 dev
vite
VITE v5.2.12 ready in 482 ms
➜ Local: http://localhost:8080/
➜ Network: use --host to expose
➜ press h + enter to show help

Bedrockを呼び出す関数を追加

backend( Bedrockを呼び出す関数を追加)

  • @aws-sdk/client-bedrock-runtime パッケージをインストールする。--save-dev オプションにより、開発中のみに必要とされ、本番環境での実行時には含まれない。
npm add --save-dev @aws-sdk/client-bedrock-runtime

added 85 packages, and audited 89 packages in 6s
2 packages are looking for funding
run npm fund for details
found 0 vulnerabilities


  • ディレクトリ、ファイルを作成する。
$ mkdir amplify/function/bedrock
$ touch amplify/function/bedrock/resource.ts
$ touch amplify/function/bedrock/handler.ts
amplify/function/bedrock/resource.ts
import { defineFunction } from '@aws-amplify/backend';

export const bedrock = defineFunction({
    name: 'bedrock',
    entry: './handler.ts',
})
amplify/function/bedrock/handler.ts
// AWS Lambdaのハンドラーとコンテキスト型をインポート
import { Context, Handler } from 'aws-lambda';

// Node.jsのストリームライブラリからWritableクラスをインポート
import { Writable } from 'stream';

// AWS SDKからBedrockRuntimeClientとInvokeModelWithResponseStreamCommandをインポート
import {
    BedrockRuntimeClient,
    InvokeModelWithResponseStreamCommand,
} from "@aws-sdk/client-bedrock-runtime";

// イベントタイプを定義しprompt 文字列を持つオブジェクト
type eventType = {
    prompt: string
}

// 使用するモデルのIDを定義
const modelId = "anthropic.claude-3-haiku-20240307-v1:0"

// Lambda関数のハンドラを非同期関数として定義
export const handler: Handler = awslambda.streamifyResponse(
    async (event: eventType, responseStream: Writable, _context: Context) => {

        // BedrockRuntimeClientのインスタンスを作成し、リージョンを指定
        const client = new BedrockRuntimeClient({ region: "us-east-1" });

        // Lambda関数へ送信するデータペイロードを定義
        const payload = {
            anthropic_version: "bedrock-2023-05-31",
            max_tokens: 1000,
            messages: [
                {
                    role: "user",
                    content: [{ type: "text", text: event.prompt }],
                },
            ],
        };

        // モデルに対するコマンドを生成し、リクエストの内容と型を指定
        const command = new InvokeModelWithResponseStreamCommand({
            contentType: "application/json",
            body: JSON.stringify(payload),
            modelId,
        });

        // APIコマンドを送信し、レスポンスを取得
        const apiResponse = await client.send(command);

        // レスポンスのボディを反復処理し、内容に基づいて条件分岐
        if (apiResponse.body) {
            for await (const item of apiResponse.body) {
                if (item.chunk) {
                    // 受け取ったチャンクデータをJSONとして解析し、ストリームに書き込み
                    const chunk = JSON.parse(new TextDecoder().decode(item.chunk.bytes));
                    const chunk_type = chunk.type;

                    // コンテンツタイプが「content_block_delta」の場合、テキストをレスポンスストリームに書き込み
                    if (chunk_type === "content_block_delta") {
                        const text = chunk.delta.text;
                        responseStream.write(text);
                    }
                } else if (item.internalServerException) {
                    throw item.internalServerException
                } else if (item.modelStreamErrorException) {
                    throw item.modelStreamErrorException
                } else if (item.throttlingException) {
                    throw item.throttlingException
                } else if (item.validationException) {
                    throw item.validationException
                }
            }
        }

        // ストリームの終了を宣言
        responseStream.end()
    }
)
  • amplify/backend.tsを編集する。
amplify/backend.ts
import { defineBackend } from '@aws-amplify/backend';
import { auth } from './auth/resource';
import { hello } from './function/hello/resource'
import { bedrock } from './function/bedrock/resource'
import * as iam from 'aws-cdk-lib/aws-iam';

const backend = defineBackend({
  auth,
  hello,
  bedrock,●追加
});

//認証されたユーザーのIAMロールの取得
const authenticatedUserIamRole = backend.auth.resources.authenticatedUserIamRole;

●ここから追加
const bedrockStatement = new iam.PolicyStatement({
  actions: ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
  resources: ["arn:aws:bedrock:us-east-1::foundation-model/*"]
})
●ここまで追加
//Lambda関数への実行権限の付与
backend.hello.resources.lambda.grantInvoke(authenticatedUserIamRole);
backend.bedrock.resources.lambda.grantInvoke(authenticatedUserIamRole);●追加

backend.bedrock.resources.lambda.addToRolePolicy(bedrockStatement)●追加

//amplify_outputs.jsonにLambda関数名を出力する。
backend.addOutput({
  custom: {
    helloFunctionName: backend.hello.resources.lambda.functionName,
    bedrockFunctionName: backend.bedrock.resources.lambda.functionName,●追加
  },
});

  • sandbox の進行状況を確認する。
✨  Total time: 0.27s
[Sandbox] Watching for file changes...
File written: amplify_outputs.json
  • CloudFormation の進行を確認する。
  • lambdaの生成を確認する。

frontend

途中で一時中断中