- 投稿日:2020-07-02T21:29:02+09:00
[LINE Bot] LIFFとリッチメニューでも管理画面が作りたい! 3 -認証編-
この記事はこの記事の続きです
[LINE Bot] LIFFとリッチメニューでも管理画面が作りたい! 2 -リッチメニュー切替-ここまでのまとめ
さて、ここまでで「認証用のLINEグループを作る」「グループへの招待・退出」でメニューを切り替える、というところまでできました。
あとはLIFFで作った管理画面に飛ばせばOKですが、当然そのさきのAPIでも認証をかけないといけません。構成図
ちなみに構成は次のようになっています。
- LIFF : ユーザ用メニューと管理者メニュー (React)
- bot : linebotのロジック(Go)
- line_api : LIFFからLINEの認証などのLINE固有の処理をするAPI (Go)
- omoinas : アプリケーションのメインロジックやmodel・repository層など (Rust)
LIFFでの認証の考え方
今回はログインしているユーザのLINE IDを見て管理者かどうかを判別します。
さて、LIFFでLINE IDを取得するにはsocial api 2.1 を使えば良さそうです。
が、公式マニュアルにもあるように、
LIFFでLINEのUserIDを取得して、それを直接サーバに送り付けてはいけません。
(直接、ユーザIDヲ指定してAPIを叩けてしまうとセキュリティ的によろしくない)上記のドキュメントにある通り、LIFFではアクセストークンを取得し、サーバー側でプロフアイルを取得してユーザIDを確認します。
LIFFとWebページを作る
LIFFを使うには、LINE Developers から新しく「LINE Login」チャネルを作成します。
なお、現在はLine Messaging APIでLIFFアプリは作れません。今回はReactで作りました。
Reac/TypescriptでLIFFを作るためのあれこれ
意外と面倒です。。。
今回は create-react-appで作りましたが、はじめGatsbyあたりを使おうとして色々読み込み方がわからず、というのがありました。。LIFFを読み込む
public/index.html<html> <head> <script src="https://static.line-scdn.net/liff/edge/2.1/sdk.js"></script> </head> ... </html>Typescript用の定義ファイルを追加する。
npm install --save liff-type環境変数にLIFFのIDを書く
.env# 開発用 REACT_APP_DEV_LIFF_ID="xxxx" # 本番環境用 REACT_APP_PRD_LIFF_ID="yyyy"環境変数の切り替えについて
LIFFの開発では、UIの確認程度ならローカルでもできますが、
結局はサーバー上で確認しないとけないため、開発環境と本番環境の構築と切り替えをしないといけません。process.env.NODE_ENVは、 npm startではdevelopmentになりますが、
npm run build すると強制的に prodcutionになる為、
今回は REACT_APP_ENVという変数を使い、npm run buildするときに環境変数を指定することにしました。# 開発環境用の変数でビルドする REACT_APP_ENV=dev npm run buildLIFF初期化
LIFF初期化の注意点としては、liff.login()をかける前はURLがエンコードされており、そのままではルーティングされません。
そのため、Routerの外側でまずLIFFの初期化を行い、LIFFがURLをデコードするときちんとルーティングされます。src/App.tsximport React from 'react'; import { BrowserRouter as Router, Route, Switch, } from 'react-router-dom'; ... 何やらかんやら初期化 function App() { liff.init({ liffId: (process.env.REACT_APP_ENV === "prd" ? process.env.REACT_APP_PRD_LIFF_ID : process.env.REACT_APP_DEV_LIFF_ID)as string }).then(() => {}) return ( <MuiThemeProvider theme={theme}> <CssBaseline /> <Router> <Switch> <Route path="/" exact> This is liff. </Route> <Route path="/:env/user/omise/:clientId/:omiseId" exact> <ユーザ用のページ /> </Route> <Route path="/:env/staff/omise/:clientId/:omiseId" exact> <管理用ペーシ /> </Route> </Switch> </Router> </MuiThemeProvider > ); } export default App;管理者用のページでアクセストークンを取得する
公式ドキュメントに従い、LIFFの初期化とアクセストークンの取得をします。
src/StaffOmise.rsimport React, { useState, useEffect } from 'react' import { useParams } from 'react-router-dom'; import { useForm, Controller } from "react-hook-form"; // ↓サーバサイドのAPI import {getOmise, Omise, setOmise, OmiseForm} from 'utils/api/omise'; ... 色々コンポーネント読み込みとか function StaffOmise() { console.log("aaaa") const {env, clientId, omiseId, charaId} = useParams<RouteParams>(); ... // ロード時にデータ読み込む処理 const load = () => { getOmise(env, clientId, omiseId, (omise: Omise) => { ... // フォームへの値の設定とか }) } // 初期化処理 useEffect(() => { liff.ready.then(() => { let accessToken = "" if (!liff.isLoggedIn()) { // ログインしていなければログインさせる。(ローカル環境ではスルーする) if (process.env.NODE_ENV === "production") { liff.login({}) } } else { accessToken = liff.getAccessToken() setToken(accessToken) } load() }) },[env, clientId, omiseId, charaId]) return { ...フォームとか } } export default StaffOmise;API呼び出す時にLINEのトークンを付与する
上記でアクセストークンを取得したので、APIのヘッダーにつけます。
src/utils/api.tsx// 更新API export function setOmise(token: string, ...パラメータ) { fetch ( env === 'prd' ? `${process.env.REACT_APP_PRD_LINE_API_HOST}/line-api/omise/set` : `${process.env.REACT_APP_DEV_LINE_API_HOST}/line-api/omise/set`, { method: "POST", mode: "cors", cache: "no-cache", headers: { 'Authorization': 'Bearer: '+token}, body: JSON.stringify({ ...更新するデータ }), } )... // thenとかcatchとか }サーバサイドで認証する
これでLIFFからLINEのアクセストークンが送信されますので、これを元にLINEのユーザIDを取得します。
line_api/set_omise.gofunc setOmise(request events.APIGatewayProxyRequest) (string, error) { // ちょっと雑ですが、ヘッダーからアクセストークンを取得します。 accessToken := strings.TrimPrefix(request.Headers["Authorization"], "Bearer: ") if accessToken == "" { // 401 return "", errors.New("access token is empty.") } // ユーザ名取得 prof, err := client.GetUserProfile(accessToken).Do() if err != nil { log.Println(err) return "", err } param = {...} param.UserID = prof.UserID ... // ラムダでRustの更新APIを呼び出し、そこでDynamoDBからユーザIDをチェックする。 payload, _ := json.Marshal(param) res, err := lambda.New(session.New()).Invoke(&lambda.InvokeInput{ FunctionName: aws.String(ARN + "hoge-" + os.Getenv("ENV") + "-setOmise"), Payload: payload, InvocationType: aws.String("RequestResponse"), }) if err != nil { log.Println(err) return "", err } }(Rustで書いたラムダは当然、このAPIからしか呼び出せないように設定しておきます)
まとめ
以上で、LIFFで管理画面を作ってリッチメニューを切り替えたり認証したりする方法を紹介しました。
システムを開発すると、何かにつけて管理画面が必要になりますし、
作ったら作ったで、やれスマホ対応しろだのメアド・パスワード機能をつけろ、というのが世の常です。
ですが、管理画面なんてそもそもシンプルな方が良いですし、パスワード管理なんてしたくありません。
(Auth系サービスもありますが、結局、招待やらアカウント管理やら手間がかかります)できるだけシンプルにしたい! という発想からの試みでした。
- 投稿日:2020-07-02T13:21:22+09:00
create-react-app+TypeScriptでeslintを実行する
自分のブログから持ってきました.
https://wally-ngm.hatenablog.com/entry/2020/07/02/131417create-react-appには既にeslintが入っているようですが, eslintrcでルールを管理したいですよね.
どうやって回すかをまとめます.
1. eslintのダウンロードし
グローバルなところへeslintをインストールしてもいいですが, 今回はプロジェクト配下にダウンロードします.
yarn add --dev eslint2. eslintの初期設定
yarn eslint initここで,
eslint init
とするとグローバルなeslintを使用することになります. どちらでも構いませんが今回はプロジェクト配下のeslintを使うようにします.対話形式に答えていくと,
.eslintrc.js
or.eslintrc.json
or '.eslintrc.yml' のどれかが自分が選んだ形式に従って作成されます.また, 最後にnpmでパッケージを諸々ダウンロードするか聞かれます. 「yes」で大丈夫ですが, その後必ず
package-lock.json
は削除しておきましょう.3. eslintのルールを設定する
こちらを参照にして, package.jsonから一部を削除して, eslintrc.jsには追記をしていきます.
[https://kic-yuuki.hatenablog.com/entry/2019/09/08/111817:embed:cite]最終的な私のpackage.jsonとeslintrc.jsは以下のようになりました.
package.json{ "name": "app", "version": "0.1.0", "private": true, "dependencies": { "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.3.2", "@testing-library/user-event": "^7.1.2", "@types/jest": "^24.0.0", "@types/node": "^12.0.0", "@types/react": "^16.9.0", "@types/react-dom": "^16.9.0", "react": "^16.13.1", "react-dom": "^16.13.1", "react-scripts": "3.4.1", "typescript": "~3.7.2" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", "lint": "$(npm bin)/eslint -c ./.eslintrc.js 'src/**/*.{ts,tsx}'", }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "devDependencies": { "@typescript-eslint/eslint-plugin": "2.x", "@typescript-eslint/parser": "2.x", "babel-eslint": "10.x", "eslint": "6.x", "eslint-config-react-app": "^5.2.1", "eslint-plugin-flowtype": "4.x", "eslint-plugin-import": "2.x", "eslint-plugin-jsx-a11y": "6.x", "eslint-plugin-react": "7.x", "eslint-plugin-react-app": "^6.2.2", "eslint-plugin-react-hooks": "2.x" } }eslintrc.jsmodule.exports = { env: { browser: true, es2020: true, }, extends: [ "eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/recommended", ], parser: "@typescript-eslint/parser", parserOptions: { ecmaFeatures: { jsx: true, }, ecmaVersion: 11, sourceType: "module", }, settings: { "import/resolver": { node: { paths: ["src"], extensions: [".js", ".jsx", ".ts", ".tsx"], }, "babel-module": { root: ["./src/"], }, }, }, plugins: ["react", "react-app", "react-hooks", "@typescript-eslint"], rules: { semi: ["error", "always"], quotes: ["error", "double"], "react/prop-types": [0], "react-hooks/rules-of-hooks": "error", "@typescript-eslint/no-unused-vars": "error", "no-unused-var": 0, "no-undef": "off", "no-use-before-define": ["off"], "@typescript-eslint/no-use-before-define": ["off"], "@typescript-eslint/explicit-function-return-type": ["off"], }, };いくつか必要なプラグインをダウンロードする必要があるので, package.jsonの
devDependencies
の中身をすべてご自身のpackage.jsonへコピーし,yarn
コマンドで必要なライブラリをダウンロードすると同じ環境になります.4. eslintを回す
設定ファイルと参照先を指定してeslintを回します
yarn eslint -c ./.eslintrc.js 'src/**/*.{ts,tsx}'
毎回これを書くのは面倒なので, package.jsonにコマンドを書きましょう
package.json
"scripts": {
"start": "react-scripts start",
....,
"lint": "$(npm bin)/eslint -c ./.eslintrc.js 'src/**/*.{ts,tsx}'"
},
これでいい感じにeslintが回ってくれます.
- 投稿日:2020-07-02T10:35:26+09:00
Next.js + TypeScript で path alias を使う
プロジェクトルートに alias を設定
next.config.jsconst path = require('path') module.exports = { webpack(config) { config.resolve.alias['@'] = path.join(__dirname) return config }, }tsconfig.json{ "compilerOptions": { ... "baseUrl": "./", "paths": { "@/*": ["./*"] } ... }, ... }使う
index.tsximport Layout from '@/components/Layout'
- 投稿日:2020-07-02T10:01:15+09:00
今からサービスを考えるなら、SE向けが堅い。少なくとも、男性向けにしよう。
最近、暇で以下のサービスを作った。
https://deau-project.herokuapp.com/このサービスを、Heroku, React, Go Gin で実装した。
Heroku なので無料だ。
請求先の登録も不要である。React 以下の、material-ui のテンプレートを使用した。
https://github.com/mui-org/material-ui/tree/master/docs/src/pages/getting-started/templates/album
https://github.com/mui-org/material-ui/tree/master/docs/src/pages/getting-started/templates/sign-in
https://react-hook-form.com/jp/サービスを開始してから、Qiita でブログを始めた。
ブログを始めた次の日のPV, UUが、500弱だった。
9割は、Qiita からである。以前の記事で書いたが、無料でサービスを開始できる。
ただ、利用者を増やすにはどうしようか考えていた。無料で広告するなら、Qiita で記事を書くといい。
ちなみに、note でも同じく記事を書いているが、
note からは数人しか来てなかった。Qiita からのアクセスは、広告に匹敵する。
もし、サービスを考える時に、この事実を知っていたら、SE向け、男性向けにしていただろう。
残念。作ったサービスはメール送信するサービス。
SEの性かもしれないが、送信までしてくれる人が多い。もし、あなたがサービスを考えている途中なら、是非、考慮してもらいたい。
- 投稿日:2020-07-02T05:04:15+09:00
Git 使い方 git push origin masterまで
Gitでのpushまでの道のり
gitの学習メモ
pushまでの手順。githubのアカウントは持っている前提。
1、リポジトリの作成
・リモートリポジトリ
・GitHubにログイン
・画面右上+ボタンを押す。
・New repositoryを選択
・リポジトリ名を付ける
・Create Repositoryを押す・ローカルリポジトリ
・ローカルのソースコードのあるディレクトリに移動
・git init でローカルリポジトリ作成リモートリポジトリの指定
・git remote add origin https ~ .git
・https ~ .git は GitHubの作ったリポジトリのCodeのページのHTTPSのボタン押したところのやつadd、commit、push
ソースコードの変更をローカルリポジトリに追加し、リモートリポジトリにローカルリポジトリを反映する。
・git add . ( . は全部のファイルって意味)
・git commit -m "何を変更したかとかのメッセージ"・git push origin master
これでリモートリポジトリへの反映