- 投稿日:2019-09-29T19:20:55+09:00
█║▌ Distributed Fault-Tolerant Authentication Management & Identification Control System
At the rate at which technology today is moving forward with the Internet speeds increasing manifold, with IoT gaining prominence and organizations more distributed across the globe than before, the authentication software, systems and architectures remain fairly primitive.
Among the many reasons attributing to this is corporates that build these authenticating systems and software hold on to these products as their main source of income. The insight and research in these areas has also been fairly mundane. Though there's been enough research funding, what's missing has been the intellect and knowledge required to build large-scale distributed and decentralized authentication systems and architectures.
Large-scale authentication systems and architectures used in building them must allow both manned [computers, tablets, phones, virtual machines etc] and unmanned [IoT devices etc] to authenticate and authorize themselves without a centralized bottleneck, as seen in authentication systems like LDAP, Active Directory and others.
As experienced on a daily basis, these centralized authentication systems are not scalable or fault-tolerant without a sane fail-over MTBF [Mean Time Between Failure] causing business disruptions on a regular and long-lasting basis.
■ What can be done about this?
Let us acquaint ourselves with AuthControl. SynchroKnot designed and developed AuthControl as a result of realizing inadequacies in the centralized authentication systems [LDAP & Active Directory].
AuthControl was designed with the following flexibility in mind:
- Ability for authentication to be either centralized, distributed, decentralized or a combination of these.
- Ability to be seamlessly and transparently scaled on-demand across the globe with no downtime.
- Ability to be used by standard operating systems within their security framework without custom or proprietary software, enhancements, modifications or hacks.
- Ability to be used across all devices that can make a simple https call. and much more.
■ What is AuthControl?
AuthControl is SynchroKnot's unique Distributed Fault-Tolerant Authentication Management & Identification Control System that serves as a scalable, secure and simple alternative to LDAP, Active Directory and other authentication systems.
In AuthControl, the user[s] can be delegated and made responsible for managing their password. Furthermore, the user's password SHA512/GOST checksum is kept encrypted.
■ Password + Pin
The user[s] can log in to their virtual machines or physical hardware [eg. computers, tablets, mobile phones etc] with their standard username and password + 5 digit unique pin.
This 5 digit pin is not set by the user, but is rather auto or manually generated per the preference of the organization. Without having to manage separate pins for each user, and the ability to change them on a regular basis, makes logging into systems and authentication for various purposes more secure without adding the burden of lengthy procedures/steps.
Depending on the nature of the circumstance, user access can be restricted/limited by simply changing the PIN.
■ Algorithmically-ascertained decentralized numeric User and Group ID
Authcontrol also has the unique capability of creating operating system specific user and group identities that are unique. For example, AuthControl can create a Linux User ID and Group ID that are unique and always return the same numeric value for the ID.
This unique numeric user and group ID is algorithmically created in a decentralized manner without having to generate, store and poll centralized or distributed databases.
Due to the uniqueness of the user and group IDs, they can be instantly checked for changes/manipulations and reinstated automatically if changed without having to poll, check and compare with central or distributed databases. It can also report/alert in the similar manner.
AuthControl's strong security is strengthened with the use of inter-leaved mapping of Usernames to their Blockchain IDs and further using blockchain cryptography [not the blockchain network] to ascertain authenticity. This is another unique feature you will not find anywhere else but with SynchroKnot.
■ Fault Tolerant
AuthControl algorithmically checks for failures across multiple geographically-dispersed locations [configurable up to 10] before returning unreachable.
■ Load Balanced
Each user or groups of users can be assigned different geographically-dispersed locations for load balancing [with additional option of fault-tolerance].
■ Scalable
Enable AuthControl in virtual or physical machines, point more users to them, and scale seamlessly and transparently across the globe.
■ Simple
Very easy to set up and manage. Works transparently with Linux PAM without modifying standard PAM modules, and is end-to-end encrypted [uses standard HTTPS for communication].
Since this is just an article for getting acquainted with AuthControl, we refrain from getting into technicalities which might be better reflected in a whitepaper.
■ Below are examples of different methods that users can log in or access resources transparently with their standard Username and Password + 5 Digit PIN:
│
├─> Graphical Login
├─> Graphical Screen Saver Login [eg. screen lock]
├─> Non-Graphical Login
├─> SUDO - Execute a command as another user
├─> SU - Super User
├─> SSH - Secure Shell
├─> SCP - Secure Copy
├─> SFTP - Secure File Transfer Protocol
├─> SSHFS - Secure Shell File System
├─> FTP - File Transfer Protocol
├─> VNC - Virtual Network Computing
├─> RDP - Remote Desktop Protocol
├─> CUPs - standards based open source printing system
├─> CRON - Execution of scheduled commands
├─> SAMBA - Windows AD and SMB/CIFS fileserver for UNIX
├─> File Manager - Create Network Place with SFTP, SAMBA and FTP
├─> All password requirements via Control Center
├─> Practically anything that uses Standard PAM for authentication!Below is a direct link to the demonstration video:
http://synchroknot.tokyo/AuthControl.mp4
Description of the demonstration:
This is a very basic impromptu demonstration of AuthControl. Here both of the virtual machines are enabled with AuthControl and show the following:
■ Login via Graphical Interface
■ Login via Non-Graphical Interface
■ Run a command with SU as another user
■ Run a command with SUDO as another user
■ Login to a remote system via SSH
■ Mount a remote filesystem via SSHFS
■ Use File Manager to create a Network Place using SFTP
All these different types of logins use AuthControl with standard Linux users and password + 5 digit pin. The basic HTTPS traffic is captured using TCPDUMP to show realtime interaction with the SynchroKnot AuthControl when the password is entered in the virtual machines for the purposes of authentication.
Note: This demo was recorded on a severely resource-constrained system. It is up to you to determine the performance.
- 投稿日:2019-09-29T19:17:10+09:00
StorybookのReactのチュートリアルを見ながら基本を勉強してみた
React、Storybook初心者です。チュートリアルをやってみただけの投稿です。よろしくお願いします
私が実際に試したコードはこちらです。
https://github.com/okumurakengo/react-storybook-sample
Storybook for React tutorial
Get started | Storybook Tutorial
Setup React Storybook
# Reactのプロジェクト作成 npx create-react-app taskbox cd taskbox # Storybookを追加 npx -p @storybook/cli sb init # port:9009 でコンポーネントエクスプローラーが起動する yarn run storybookReuse CSS
1 チュートリアルを進める為に、cssをこちらかコピペする
↓このcssをコピーして
https://github.com/chromaui/learnstorybook-code/blob/master/src/index.css
./src/index.css
にコピペする2
./.storybook/config.js
でindex.css
を読み込む.storybook/config.jsimport { configure } from '@storybook/react'; // 追加 import '../src/index.css'; // automatically import all files ending in *.stories.js configure(require.context('../src/stories', true, /\.stories\.js$/), module);css読み込み前
css読み込み後
Add assets
フォントとアイコンも読み込む
↓ここのiconとfontフォルダをそのまま
./public/
フォルダに追加しておく
https://github.com/chromaui/learnstorybook-code/tree/master/publicBuild a simple component
Build a simple component | Storybook Tutorial
Task
タスクコンポーネントを作成する
Get setup
src/components/Task.js
とsrc/components/Task.stories.js
を作成するsrc/components/Task.jsimport React from 'react'; export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) { return ( <div className="list-item"> <input type="text" value={title} readOnly={true} /> </div> ); }src/components/Task.stories.jsimport React from 'react'; import { storiesOf } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import Task from './Task'; export const task = { id: '1', title: 'Test Task', state: 'TASK_INBOX', updatedAt: new Date(2018, 0, 1, 9, 0), }; export const actions = { onPinTask: action('onPinTask'), onArchiveTask: action('onArchiveTask'), }; storiesOf('Task', module) .add('default', () => <Task task={task} {...actions} />) .add('pinned', () => <Task task={{ ...task, state: 'TASK_PINNED' }} {...actions} />) .add('archived', () => <Task task={{ ...task, state: 'TASK_ARCHIVED' }} {...actions} />);Storybookはコンポーネントと、それのストーリーで構成されます。
コンポーネント1つに対して、必要な数のストーリーを作成できます。
- Component
- Story
- Story
- Story
Config
./.storybook/config.js
を変更して作成した、stories.jsファイルを読み込めるようにします.storybook/config.jsimport { configure } from '@storybook/react'; import '../src/index.css'; const req = require.context('../src', true, /\.stories.js$/); function loadStories() { req.keys().forEach(filename => req(filename)); } configure(loadStories, module);このように表示されます。
storiesOf
とするたびにメニューが増え、
add
とするたびに下の階層にメニューが増えてきます。Build out the states
storybookでコンポーネントを表示までできたので、
次にコンポーネントに状態やイベントを設定、UIの見た目を設定します。
storybookで見た目を確認しながら作成することができます。src/components/Task.jsimport React from 'react'; export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) { return ( <div className={`list-item ${state}`}> <label className="checkbox"> <input type="checkbox" defaultChecked={state === 'TASK_ARCHIVED'} disabled={true} name="checked" /> <span className="checkbox-custom" onClick={() => onArchiveTask(id)} /> </label> <div className="title"> <input type="text" value={title} readOnly={true} placeholder="Input title" /> </div> <div className="actions" onClick={event => event.stopPropagation()}> {state !== 'TASK_ARCHIVED' && ( <a onClick={() => onPinTask(id)}> <span className={`icon-star`} /> </a> )} </div> </div> ); }storybookでイベントを拾えたのを確認でき、状態ごとに見た目が変わることも確認できました。
Specify data requirements
コンポーネントの見た目、イベントを作成完了まで行い、その後に
propTypes
を設定すると良いようです。src/components/Task.jsimport React from 'react'; import PropTypes from 'prop-types'; export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) { return ( <div className={`list-item ${state}`}> <label className="checkbox"> <input type="checkbox" defaultChecked={state === 'TASK_ARCHIVED'} disabled={true} name="checked" /> <span className="checkbox-custom" onClick={() => onArchiveTask(id)} /> </label> <div className="title"> <input type="text" value={title} readOnly={true} placeholder="Input title" /> </div> <div className="actions" onClick={event => event.stopPropagation()}> {state !== 'TASK_ARCHIVED' && ( <a onClick={() => onPinTask(id)}> <span className={`icon-star`} /> </a> )} </div> </div> ); } Task.propTypes = { task: PropTypes.shape({ id: PropTypes.string.isRequired, title: PropTypes.string.isRequired, state: PropTypes.string.isRequired, }), onArchiveTask: PropTypes.func, onPinTask: PropTypes.func, };※参考ページによると、この後にjestによるTask.jsのコンポーネントを書くという流れのようですが、今回はそこまでやりませんでした。
Assemble a composite component
Assemble a composite component | Storybook Tutorial
コンポーネントを組み合わせた時の場合を見ていきます。
Tasklist
Taskを組み合わせたTaskListをstorybookで表示します
Get setup
src/components/TaskList.js
とsrc/components/TaskList.stories.js
を作成するsrc/components/TaskList.jsimport React from 'react'; import Task from './Task'; function TaskList({ loading, tasks, onPinTask, onArchiveTask }) { const events = { onPinTask, onArchiveTask, }; if (loading) { return <div className="list-items">loading</div>; } if (tasks.length === 0) { return <div className="list-items">empty</div>; } return ( <div className="list-items"> {tasks.map(task => <Task key={task.id} task={task} {...events} />)} </div> ); } export default TaskList;src/components/TaskList.stories.jsimport React from 'react'; import { storiesOf } from '@storybook/react'; import TaskList from './TaskList'; import { task, actions } from './Task.stories'; export const defaultTasks = [ { ...task, id: '1', title: 'Task 1' }, { ...task, id: '2', title: 'Task 2' }, { ...task, id: '3', title: 'Task 3' }, { ...task, id: '4', title: 'Task 4' }, { ...task, id: '5', title: 'Task 5' }, { ...task, id: '6', title: 'Task 6' }, ]; export const withPinnedTasks = [ ...defaultTasks.slice(0, 5), { id: '6', title: 'Task 6 (pinned)', state: 'TASK_PINNED' }, ]; storiesOf('TaskList', module) .addDecorator(story => <div style={{ padding: '3rem' }}>{story()}</div>) .add('default', () => <TaskList tasks={defaultTasks} {...actions} />) .add('withPinnedTasks', () => <TaskList tasks={withPinnedTasks} {...actions} />) .add('loading', () => <TaskList loading tasks={[]} {...actions} />) .add('empty', () => <TaskList tasks={[]} {...actions} />);
- デフォルト状態のリスト
- pinned状態のタスクありのリスト
- リスト読み込み中
- リストが0個
↑の状態でそれぞれstorybookで見れるとこまでできました。
Build out the states
状態ごとのコンポーネントのUIを作り込んでいきます。
src/components/TaskList.jsimport React from 'react'; import Task from './Task'; function TaskList({ loading, tasks, onPinTask, onArchiveTask }) { const events = { onPinTask, onArchiveTask, }; const LoadingRow = ( <div className="loading-item"> <span className="glow-checkbox" /> <span className="glow-text"> <span>Loading</span> <span>cool</span> <span>state</span> </span> </div> ); if (loading) { return ( <div className="list-items"> {LoadingRow} {LoadingRow} {LoadingRow} {LoadingRow} {LoadingRow} {LoadingRow} </div> ); } if (tasks.length === 0) { return ( <div className="list-items"> <div className="wrapper-message"> <span className="icon-check" /> <div className="title-message">You have no tasks</div> <div className="subtitle-message">Sit back and relax</div> </div> </div> ); } const tasksInOrder = [ ...tasks.filter(t => t.state === 'TASK_PINNED'), ...tasks.filter(t => t.state !== 'TASK_PINNED'), ]; return ( <div className="list-items"> {tasksInOrder.map(task => <Task key={task.id} task={task} {...events} />)} </div> ); } export default TaskList;今回はただコピペしただけですが、storybookでコンポーネントの表示を見ながら作成できるのでやりやすいと思いました。
Data requirements and props
コンポーネントの見た目、イベントを作成完了まで行ったので、
propTypes
も設定しておくsrc/components/TaskList.jsimport React from 'react'; import Task from './Task'; import PropTypes from 'prop-types'; function TaskList({ loading, tasks, onPinTask, onArchiveTask }) { const events = { onPinTask, onArchiveTask, }; const LoadingRow = ( <div className="loading-item"> <span className="glow-checkbox" /> <span className="glow-text"> <span>Loading</span> <span>cool</span> <span>state</span> </span> </div> ); if (loading) { return ( <div className="list-items"> {LoadingRow} {LoadingRow} {LoadingRow} {LoadingRow} {LoadingRow} {LoadingRow} </div> ); } if (tasks.length === 0) { return ( <div className="list-items"> <div className="wrapper-message"> <span className="icon-check" /> <div className="title-message">You have no tasks</div> <div className="subtitle-message">Sit back and relax</div> </div> </div> ); } const tasksInOrder = [ ...tasks.filter(t => t.state === 'TASK_PINNED'), ...tasks.filter(t => t.state !== 'TASK_PINNED'), ]; return ( <div className="list-items"> {tasksInOrder.map(task => <Task key={task.id} task={task} {...events} />)} </div> ); } TaskList.propTypes = { loading: PropTypes.bool, tasks: PropTypes.arrayOf(Task.propTypes.task).isRequired, onPinTask: PropTypes.func.isRequired, onArchiveTask: PropTypes.func.isRequired, }; TaskList.defaultProps = { loading: false, }; export default TaskList;※参考ページによると、この後にjestによるTaskList.jsのコンポーネントを書くという流れのようですが、今回はそこまでやりませんでした。
最後まで読んでいただいてありがとうございました
- 投稿日:2019-09-29T17:53:46+09:00
初めてのホームページ開発手記 part0
経緯
知り合いが農業をやっており、事業拡大を今後考えるためにホームページを作成したいという話をされた。
自分は普段画像処理ミドルウェアの開発なのでWeb系はHTML, CSS, javascriptが読み書きできるくらい、node.jsはちょっと触ったことあるレベルだから正直わからん、、、けどスキルアップしたいということでまずはモックを作ってみるよ!ということにした。
開発環境
開発マシンどうする
とりあえず自分のPCを汚したくなかったので、virtual boxで仮想マシンを構築して開発する方針にする
virtual boxにubuntu18.04を入れてみたところ重い・・・自分のPCはメモリが8GBしかないためちょっときついか、、、→ xUbuntuは軽い、という記事をみたので virtual box + xUbuntu18.04 で開発してみることにする。
なにつかって開発すればいいの?
調べてみたところ、WordPressを使用してホームページを作ることが多いらしい、ブログとかは特に。
CMS(Contents Management System)というらしい、専門的な知識がない人でも簡単にホームページを作れるようになる、というところが目的らしい。でも自分は、javascriptコードとか書いてガチャガチャ動かせるようにしたかったのでちょっと違うかな、という感じ。一応WordPressでもできるみたいだけど、、、webアプリっぽい感じで開発したかった。
あとWordPress自体がPHPベースで作られているみたいで、PHPかぁ~という感じであった。会社であんま使われているの聞いたことないから勉強してもなぁと。有名どころのweb開発フレームワークとしては
PHP Laravel
Ruby on Rails
あたりが有名みたいだった。個人的にjavascriptを極めていきたかったのでサーバーサイドもjavascriptでやっていきたいということで調べたところ出てきたキーワード node.js, Express, jQuery, Angular, vue.js, React...沢山あるけど微妙に役割が違うみたいよくわからん、、、
どれがフレームワークで、どれがライブラリ?サーバーサイド?クライアントサイドで動作?ということで整理する?というかそもそもWebサイトを作りたいのか、Webアプリを作りたいのかにもよって使うものが違う気がするのでそこも整理したい。
私はWebサイトとWebアプリどっちつくるの?
今回やりたいと考えたこと
- 情報の発信
生産者のこだわりとかアクセスマップとか、そういう情報を閲覧者に伝えたい。- 問い合わせフォーム、取り扱い商品の販売のようにレスポンシブな感じ
以上を踏まえて以下のサイトを参考にすると、対話成分が入る場合はWebアプリケーションという考えとして設計していった方が良いみたいだ。
参考リンク: IBM様 Web サイトから Web アプリケーションへの変換ということで今回は情報提供を中心としたWebアプリケーション開発をすることにします!!
node.jsってなに?
- Node.jsとはサーバーサイドでjavascriptを使用できるようにしたもの、Webサーバーとしても動作する。
- あくまで実行環境を提供するようなイメージであって、これ自体はフレームワークとは言わないようである(フレームワークとしては大きすぎる)。
Express
Node.js上で動作するMVCフレームワーク。これはフレームワーク、ふむ。サーバーサイドにいれるものということか。
MVCとはModel View Controller モデル・ビュー・コントローラ を意味していて、そういうデザインパターンのこと、UIとロジックを分ける、みたいなイメージですね。
「Express」「Meteor」「Sails」がJavaScriptの三大フレームワークらしい。
Expressを使用したCMSとしてKeyStoneJSというものがあるみたい、本番では使ってみようかな。
jQuery
- JavaScriptのライブラリの一つ。ふむ、これはフレームワークではないのね、なるほどね。
- フロントエンド(クライアントサイド)で使用されることが多いらしい、JavaScriptのライブラリなのでサーバーサイドでも使えるらしい。
Angular
シングルページアプリケーション (SPA)の開発に向いているJavaScriptフレームワークの一つ、なるほどこれはフレームワーク。
だけどこれはフロントエンドフレームワーク、つまりExpressとは用途が違うということSPAとはクライアント側でHTMLを動的生成するアプリケーションのこと、一々サーバー側には問合せしない、なるほど。
Angular / vue.js / Reactで比較されることが多い。AngularはGoogleが開発している。
ルーティングなど必要な機能が全部入ったフルスタックフレームワークらしい。実はクライアントサイドでルーティングという意味がよくわかってないです、サーバーサイドのルーティングと共存するの?
React
FaceBookが開発
Angularと同じくフロントエンドで使用されるJavaScriptフレームワークの一つ、として扱われることも多いが、FaceBookはJavaScriptライブラリ、と言っているらしいのでライブラリとして考えたほうが良い。
UIに特化しているみたいなのでライブラリの方がしっくりくる。
Vue.js
Angularと同じくフロントエンドで使用されるJavaScriptフレームワークの一つ。
Googleの中の人(エヴァン・ヨー氏)がAngularの前身のAngularJSを開発に携わったが、そこから好ましい部分を抜き出して再構築しなおしたものがVue.jsらしい。
エヴァン氏世の中に貢献しすぎ、すごい。UIに特化しているようで、ルーティング等は別のライブラリを使用するらしい。じゃあReactと同じでライブラリじゃない?と思ったけど使ってみたら違いがわかるのかもです。
こちらの記事に比較がありました、わかりやすいです。
最終的に決めた環境
クライアントサイドのルーティングとサーバーサイドのルーティングを混ぜるとちょっと開発が大変そうにみえたので、初学者ということもあるのでまずはサーバーサイドでレンダリングすることに注力したいと思います。
SPAだと機能提供メインのアプリって感じがしますし。
ということで
サーバーサイド
node.js, Express でいきます!DB使う場合はMongoDBと相性がよさそう。クライアントサイド
CSSフレームワークはbootstrapでレスポンシブデザインなサイトを目指します!
- 投稿日:2019-09-29T17:50:08+09:00
Angular8+Rails5でアプリを作ってみる
AngularとRailsを組み合わせてみる
前回の記事ではAngular+NestJSの組み合わせについてピックアップをしましたが、今回はRuby on Rails5とAngular8の組み合わせでwebアプリを作成している記事があり、読んでいてとても面白い内容だったので翻訳に挑戦しました!どちらも個人的に気に入っているフレームワークなので、世の中にこの組み合わせでの開発が増えてくると嬉しいです。
Angular8とRails5 (*以下記事の翻訳)
あたなはどうか知らないが、学びやすく、素早く書けて、オープンソースという理由で私はRuby on Railsが大好きだ。また、Ivyというクールな新しいレンダーエンジンを導入したAngularの新バージョン、Angular8のリリースにもワクワクしている。
もしあなたが私と同じでこれら2つのフレームワークに関する興味をわかってくれて、でも、その素晴らしい2つをどう始めたら良いのかさっぱりわからない場合は、ぜひこれを読んでほしい。
さっそく初めてみよう!
慣習的にJavascriptフレームワークと一緒にRailsを使う場合は、webpackが必要とされていた。しかし、Railsにはwebpackerと呼ばれるこれにうってつけのgemがあり、Angularプロジェクトを動かしてくれる。しかし、最新バージョン(7系以上)のAngularでは内部でwebpackシステムを持っており、そのようなプロジェクト構成が、素晴らしいAngularCLIを使う妨げとなってしまうのだ。
その代わりに私たちがしなければならないのは、Angularからの出力ファイルを受け取り、それらを提供するコントローラを備えたAPIとしてのRailsアプリを使うことだ。
始める前に下記をインストールしてほしい。
- node (8.9以上)
- NPM (5.5.1以上)
- Angular CLI
- Rails 5+ (インストールにRVMまたはRbenvを使うことをおすすめします)
- Postgresql
1. Angularプロジェクトを作成する
好きなディレクトリの場所でターミナルを開いて、Rails/Angularプロジェクトを作成する。
ng new rails-angular-project
Angular routingを使うか聞かれたら、迷わず「YES」を押しちゃってください!(かなり良いですよ!)
その次に、好きなスタイルシートの形式を選択して下さい。(個人的にはSCSSで進めてます)
そして、全て完了したら先ほど作ったプロジェクトにジャンプしましょう!
cd rails-angular-project
2. Railsプロジェクトを作成する
では、現在のディレクトリにRailsプロジェクトを作成しましょう!
rails new . -database=postgresql
これによって、Angularプロジェクトの内部にRailsプロジェクトを作成し始めるが、全てのマージを拒否した場合、コンフリクトが発生する。
Railsプロジェクトが作成されたなら、.gitignoreファイルにいくつかファイルを追加する必要があります。
.gitignoreファイルを開いて、下記をコピペして下さい。# Ignore bundler config. /.bundle # Ignore all logfiles and tempfiles. /log/* /tmp/* !/log/.keep !/tmp/.keep # Ignore uploaded files in development /storage/* !/storage/.keep .byebug_history # Ignore master key for decrypting credentials and more. /config/master.key # Ignore application configuration /config/application.yml3. 上記2つを合体する
今の段階で、Angularプロジェクトと一緒になったRailsアプリとなっており、これはAPIとしてのRailsを使用し提供している一方で、Angularフレームワークを用いたSPAの利益も享受していることと言える。しかしその前にRailsにAngularが提供するファイルにアクセスできるようにしなければならない。
通常はAngularアプリでは、
ng serve
を使用して開発を進めるのだが、今回はng build
を使用してRailsのパブリックディレクトリに出力するように設定する。ルートディレクトリにある
Angular.json
を開いて、出力先ディレクトリをdist/rails-angular-app
からpublic
に変更する。angular.json... "options": { "outputPath": "public", ...次に、app/controllersに
static_controller.rb
という名前のファイルを作成する。app/controllers/static_controller.rbclass StaticController < Rails::ApplicationController def index render file: Rails.root.join('public', 'index.html') end endstatic_controllerはAngularによって提供される
index.html
をレンダリングする役目を持ちます。そして、下記のコードを
routes.rb
に追加して下さい。routes.rbRails.application.routes.draw do get '*other', to: 'static#index' endこれで、Railsにルートをキャッチさせて、static_controllerにリダイレクトすることができます。また、上記のコードによってapiに必要となるどのルートも定義することができるのです。でもそれは次の機会に回しましょう!
4. Foremanを使って便利にする
Foremanを導入することによって、Webアプリを提供しやすくしましょう。
gemに
foreman
を追加することによってインストールして下さい。Gemfile... # Use foreman gem 'foreman' ...
bundle install
で新しいgemをインストールします。
bundle install
Procfileと呼ばれるファイルを新しくルートディレクトリに追加します。これは組み合わせされる全てのコマンドをforemanが読み込めるようにするためのファイルです。
Procfileweb: rails s -p 3000 client: ng build --watch=trueこれでforemanを起動できます。
foreman start
RailsアプリとAngularアプリが同時に起動されるはずです。そして、
localhost:3000
にアクセスすると以下のような画面が表示されるでしょう。完了です!これで自由にWebアプリを開発することができますし、apiにシンプルかつパワフルなフレームワークを与えるのと同時に、Angular CLIの力を用いることができます。
5.終わりに
今回、Rails5のバックエンドとAngular8のフロントエンドで、同じディレクトリに存在し、それぞれの力を最大限に活用するアプリケーションを作りました。このチュートリアルがあなたにとって役に立てれば幸いです。
参考記事
- 投稿日:2019-09-29T15:37:46+09:00
ESLint v6.5.0
前 v6.4.0 | 次 (2019/10/12 JST or 2019/10/26 JST)
ESLint 6.5.0 has been released: https://t.co/D7WheS5wmc
— ESLint (@geteslint) September 29, 2019ESLint
6.5.0
がリリースされました。
小さな機能追加とバグ修正が行われています。質問やバグ報告等ありましたら、お気軽にこちらまでお寄せください。
? 本体への機能追加
--env-info
CLI option? #12270
ESLint の実行環境を表示する CLI オプションが追加されました。
バグ報告の際にご利用ください。
$ eslint --env-info Environment Info: Node version: v12.11.0 npm version: v6.11.3 Local ESLint version: v6.5.0 Global ESLint version: Not found? 新しいルール
特になし。
? オプションが追加されたルール
use-isnan
enforceForSwitchCase
浮動小数点数の仕様により、
NaN
との比較は常にfalse
になります。これを報告するuse-isnan
ルールに、switch
文も報告するオプションが追加されました。/*eslint use-isnan: [error, { enforceForSwitchCase: true }]*/ //✘BAD switch (foo) { case NaN: bar(); break; }✒️
eslint --fix
をサポートしたルール特になし。
⚠️ 非推奨になったルール
特になし。
- 投稿日:2019-09-29T15:34:51+09:00
連絡先の検索
// html
<label for="search">連絡先の名前 ; </label> <input type="text" id="search"> <button>検索</button> <p></p>// javascript
'use strict';
{
var contacts = ['クリス:2232322','サラ:3453456','ビル:7654322','メアリー:9998769','ダイアン:9384975'];
var para = document.querySelector('p');
var input = document.querySelector('input');
var btn = document.querySelector('button');btn.addEventListener('click', function(){ var searchName = input.value; input.focus(); for(let i = 0; i < contacts.length; i++){ var splitContact = contacts[i].split(':'); //ここで個々の項目を項目が2つの配列にする if(splitContact[0] === searchName){ para.textContent = splitContact[0] + 'の電話番号は ' + splitContact[1] + ' です。'; break; }else{ para.textContent = '連絡先が見つかりません。'; } } });}
- 投稿日:2019-09-29T15:10:58+09:00
指定の要素を取得する方法
まえがき
引き続き発掘作業中に見つけたメモ書き。
素のJavaScriptでどうにかしなきゃ行けない時にいろいろメモしていたらしい。
以下、jQueryが使えるなら、そっち導入してやる方が良いと思う。id指定で取得
var test = document.getElementById("idName"); var url = test.getAttribute("href"); url += url + "いじる。加えたり"; test.href = url; // aタグの位置に戻す※idはhtml一つのファイルにつき一意となっている。
class指定で取得
- ちょっと調べるのめんどかった。下の方に記述したやり方の方がスマート
var test = document.getElementsByClassName("className"); test = Array.prototype.filter.call(test, function(test){ return test.nodeName === "A"; // aタグの意。DIVとかもある。この場合は、classNameに一致するaタグという意。 }); for (var i =0; i < test.length; i++) { var url = test[i].getAttribute("href"); url += url + "いじる。加えたり"; test[i].href = url; // aタグの位置に戻す }
getElementsByClassName()
は、 Array.prototypeメソッドに与えて利用することができるtest.nodeName === "A"
- nodeNameでclass内部の要素を指定できる。くわしくはその都度ググるか、コンソール上で名前確認する。
- classは複数指定可能なので、配列で返ってくる。そのためfor文でまわす。
↑よりも、まずタグ指定で要素を配列にまとめて、指定のclassが存在するか確認する方がシンプル
① var t = document.getElementTagname('a').... ※うろ覚え ② t.getAttribute('class') && t.getAttribute('class').indexOf('クラス名')タグ指定で取得
var test = document.getElementsByTagName("a"); for (var i =0; i < test.length; i++) { var url = test[i].getAttribute("href"); url += url + "いじる。加えたり"; test[i].href = url; // aタグの位置に戻す }自分へ
見直しが必要です。暇ができたらやってください。
- 投稿日:2019-09-29T15:04:14+09:00
...(三点リーダー的な奴)の使い方
スプレットオペレーター
ES6の記法
http://analogic.jp/spread-operator/
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax配列を連結したり、連結して関数に投げたりできる。
function getTest(x,y,z) { return x + y + z; }; var test = [1,2,3]; console.log(getTest(...test)); // output : 6
- 投稿日:2019-09-29T14:50:42+09:00
cookieの操作可能範囲について
まえがき
2年ほど前に調べたcookieの操作可能範囲。
発掘したので、そのままぺたり。
操作手段はJavaScript。参考
- http://www.imymode.com/exp/cookie.html
- https://qiita.com/uryyyyyyy/items/1c32afea4240a14d57e4
- https://blog.tokumaru.org/2011/10/cookiedomain.html
- https://triple-underscore.github.io/RFC6265-ja.html#section-4.1.2.3
前提
cookieのpath指定はすべて「/」である。
cookieの読み書き範囲の調査
■ トップドメイン(.com, .jp等)で指定してcookie付与は不可
document.cookie = "_testsunTop=hogehoge; path=/; domain=.com;";=> 付与できない
■ ドメインで指定してcookieを付与した場合、指定した階層以下からは参照可能
参照可否
同一 上位 下位 〇 × 〇 現在の場所
test.aaa.domain.com
document.cookie = "_testsun=ttttt; path=/; domain=.aaa.domain.com;";参照可能範囲
test.aaa.domain.com
tttt.aaa.domain.com
aaa.domain.com
参照不可
domain.com
■ 現在いる場所のドメインと指定するドメインの場所が異なる場合、書き込み不可
現在の場所
test.aaa.domain.com
document.cookie = "_testsunAno=anotherdomain; path=/; domain=.tttt.aaa.domain.com;";■ ドメインを指定せずにcookieを書き込むと、そのドメインからしか見ることができない
現在の場所
aaa.domain.com
document.cookie = "_testsunNoSetDomain=foobaa; path=/;";
- 設置場所
aaa.domain.com
- サブドメイン
test.aaa.domain.com
【IEの場合】、ルートドメインでcookieが発行されるとサブドメインまで読み込むことができる。
① root.com/hogehoge.htmlで以下のように設定
document.cookie = "_sun_tes_no_set_domain=noDomain; path=/"; document.cookie = "_sun_tes_no_priod=setNoDomain; domain=root.com; path=/"; document.cookie = "_sun_tes_set=setDomain; domain=.root.com; path=/";② sub.root.com/fugafuga.htmlでcookieを確認すると、すべての値を確認可能。
削除のことば
expires=Thu, 01 Jan 1970 00:00:00 GMT
- 投稿日:2019-09-29T14:49:51+09:00
Node.jsでModule hack (require hack)する
Node.jsには
require
というモジュールを読み込む機能があります。これはNode.js Module APIで提供されている機能で、割と簡単にハックすることができます。よく Module hack とか require hack と呼ばれるものです。
- @babel/register
- ts-node/resigter
などが有名どころでしょうか。これらのモジュールでModule hackをすると、本来のNode.jsでは動作しないTypeScriptや拡張されたJavaScriptが動作します。
この記事では Module hack のやり方について説明します。
Module hackのやり方
// TypeScriptの場合 export const Module = require('module') as anyまず
module
を読み込みます。拡張子に応じたハックをするのは簡単です。
Module._extensions[ext] = function(m: any, filename: string) { return m._compile('console.log("hoge")', filename) }先ほどから、Module を
as any
でany
アサーションをしたり、今回の関数の第一引数m
もanyで定義していますが、これは、@types/node
で定義されているModule
型の定義では、非公開プロパティ、たとえば_extensions
や_compile
にアクセスできないためです。実例
// test.js const Module = require('module') Module._extensions['.js'] = function(m, name) { return m._compile('console.log("piyopiyo")', name) } require('./hoge')// hoge.js console.log('hoge')これら2つのファイルを同じディレクトリにおいて、
node test.js
を実行すると、Module hack をしてなければ本来hoge
と表示されるところがpiyopiyo
と表示されます。解説
Module._extensions
は、拡張子ごとにファイルの処理をする関数群です。.js
のようにドット込みの拡張子を指定します。ts-node/registerや@babel/registerのように、ソースコードをトランスパイルして実行する場合には、大体定番となるのが、
Module._extensions['.js'] = function(m, name) { return m._compile('console.log("piyopiyo")', name) }のように最終的にNode.jsが解釈できる状態にまでトランスパイルしたソースコードを、
m._compile(code, name)
のようにすることです。補足
Module._extensions
を書き換えると、もどす時に工夫は必要となります。export const originalExts: { [props: string]: any } = {} Object.keys(Module._extensions).forEach(ext => { originalExts[ext] = Module._extensions[ext] })大体はこのように、本来の関数を保存しておきます。
もっとも、このような hack を元に戻す必要がある機会はあまりないでしょう。
モジュール読み込み自体をhackする
ここまでの hack で、拡張子ごとの hack は可能になりましたが、モジュールそのものを hack するためには別の隠しプロパティにアクセスする必要があります。
const Module = require('module') const originalLoad = Module._load Module._load = function(name, parent, isMain) { if (name === 'hoge') { return { hoge: () => console.log('hoge') } } return originalLoad(name, parent, isMain) } const { hoge } = require('hoge') hoge()
Module._load
を書き換えると、モジュール読み込みそのものにちょっかいを出せます。ただし本来の関数は保存しておいた方が良いため、const originalLoad = Module._load
で保存しておきます。このコードでは、
hoge
という名前のモジュールだけhackします。他のモジュールを読み込んだ場合は、従来読み込むべきモジュールが読み込まれます。任意のソースコードをモジュールとして返す
先ほどのhackでは、ダイレクトに書いたコードをそのまま実行しています。もちろん、それはそれで用途もあるかもしれませんが、拡張子hackのときと同じように任意のコードを実行して、その結果を返したいときもあるでしょう。
const module = new Module() module._compile(code, filename) return module.exportsさきほどもでてきた
_compile
は、Node.jsの実行環境に応じた処理をやってくれるので便利です。同等の処理を自前、たとえばeval
Function
vm.runInContext
などでやろうとするとかなり面倒です。注意点
ここに書いた undocumented なものです。これで今後も含めて正しく動作することは保証されていません。また他の黒魔術を使ったソースコードで問題が生じる可能性があります。
pirates
というnpmモジュールを使うと、より安全に Module hack を試すことはできます。自前でやるよりはいいかもしれません。参考: https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js
宣伝
技術書典7で出した本のPDF電子版を、Pixiv Boothで頒布しております。よろしければどうぞ。
- 投稿日:2019-09-29T14:23:50+09:00
カレンダー機能
..html
<select name="month" id="month"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> <option value="4">4</option> <option value="5">5</option> <option value="6">6</option> <option value="7">7</option> <option value="8">8</option> <option value="9">9</option> <option value="10">10</option> <option value="11">11</option> <option value="12">12</option> </select>ulタグ
h1タグ
..javascript
'use strict';
{
var select = document.querySelector('select');
var list = document.querySelector('ul');
var h1 = document.querySelector('h1');select.onchange = function() { var choice = select.value; // 条件式をここに書く var days = 31; if(choice === '4' || choice === '6' || choice === '9' || choice === '11'){ days = 30; }else if(choice === '2'){ days = 28; } createCalendar(days, choice + ' 月'); } function createCalendar(days, choice) { list.innerHTML = ''; h1.textContent = choice; for (var i = 1; i <= days; i++) { var listItem = document.createElement('li'); listItem.textContent = i; list.appendChild(listItem); } } createCalendar(31,'1 月'); }..css
body{
padding: 0;
margin: 0;
box-sizing: border-box;
}
ul{
padding: 0;
margin: 0;
width: 100%;
float: left;
}
ul li{
list-style: none;
background: pink;
text-align: center;
margin: 3px;
float: inherit;
width: 20%;
user-select: none;
}
- 投稿日:2019-09-29T12:50:26+09:00
redux-sagaのthrottleとdebounceを試してみる
はじめに
redux-sagaのAPI referenceを眺めていて、読んだだけでは動きがイメージしにくかったthrottleとdebounceについて、実際に動かしてイメージを掴んでみました。
(英語難しい)
実際の開発で使用したわけではないので、サンプルを作って動かしてみただけになります。サンプルのソースはこちらからどうぞ。使用したredux-sagaのバージョン: 1.0.5
TL;DR
基本的にはどちらのAPIもtakeと同様にactionのdispatchを待ち受けてタスクを起動します。同じactionが短時間内に複数dispatchされた際の挙動が異なります。
- throttle: actionがdispatchされたらタスクを起動します。指定時間内に同じactionがdispatchされた場合はタスクを起動せずに最新のactionを1個だけ保持しておき、指定時間経過後にタスクを起動します。
- debounce: actionがdispatchがされたら、actionを保持して指定時間待ってからタスクを起動します。待っている間に同じactionがdispatchされた場合は、新しいactionを保持してまた指定時間待ちます。
サンプルコード
文字だけだと「なるほど、わからん」状態になるので、実際に動かしてみました。
サンプルは前回の記事で使用したものにthrottleとdebounceを追加したものを使用しました。throttleとdecounceのボタンをクリックしたらそれぞれ
onClickThrottleButton
、onClickDebounceButton
が実行されます。sampleContainer.js// 省略 onClickThrottleButton: () => { let count = 0 const interval = setInterval(() => { dispatch(throttleSampleStart(count)) count++ if(count >= 6) { clearInterval(interval) } }, 500) }, onClickDebounceButton: () => { let count = 0 const interval = setInterval(() => { dispatch(debounceSampleStart(count)) count++ if(count >= 6) { clearInterval(interval) } }, 500) }, // 省略sampleSaga.js// 省略 function* handleThrottleSampleStart() { yield throttle(1800, THROTTLE_SAMPLE_START, runThrottleSampleStart) } function* runThrottleSampleStart(action) { console.log(`take action ${JSON.stringify(action)}`) yield call(sleepAsync, action.payload.count) yield put(throttleSampleSuccess()) } function* handleDebounceSampleStart() { yield debounce(1200, DEBOUNCE_SAMPLE_START, runDebounceSampleStart) } function* runDebounceSampleStart(action) { console.log(`take action ${JSON.stringify(action)}`) yield call(sleepAsync, action.payload.count) yield put(debounceSampleSuccess()) } const sleepAsync = async (count) => { await new Promise(r => setTimeout(r, 5000)) } // 省略動作確認
throttle
実行結果は以下になります。まずdispatchされたaction(payloadのcountが0)でタスクが起動し、1800ミリ秒待ちます。その間dispathされたaction(payloadのcountが1、2、3)ではタスクは起動しません。
1800ミリ秒経過後、保持していた最新のaction(payloadのcountが3)で再度タスクを起動します。画像ではcount4のactionではタスクが起動されていません。これは、指定時間経過後にタスク(count3のaction)が起動されていますが、このタスクが起動してからも指定時間が経過するまで同じactionを保持する状態になっているからです。(ややこしい‥)
count3のタスクが起動してから1800ミリ秒以内にcount4と5のactionがdispatchされているので、最新のaction(countが5)が保持されて1800ミリ秒経過後にタスクが起動されているようです。debounce
実行結果は以下になります。throttleと比較していくらかシンプルですね。同じactionがdispatchされる度に最新のactionを保持して新たに指定秒数待ちます。待っている間に同じactionが来なかった場合にタスクを起動しています。
まとめ
throttleはtakeEveryの代わりに使えそうだと感じました。takeEveryはdispatchされたactionをすべて拾うので、負荷の面でちょっと心配がありますが、throttleならある程度コントロールができます。そのため、actionを取りこぼしたくない、かといって負荷もあまりかけたくない、といった場面では出番がありそうですね。
debounceは‥ちょっと思いつきませんでした。開発を進める中で有効に活用できる場面が出てきたらまた紹介したいと思います。
- 投稿日:2019-09-29T12:33:39+09:00
PythonのフレームワークFlaskを使用してWebアプリ作成の物語(6)非同期通信でFlaskにアクセスしてよう
目的
- 非同期通信でflaskに問い合わせをしてみよう
本編
前回まで
PythonのフレームワークFlaskを使用してWebアプリ作成の物語(5)nginxの登場
前回まではnginxを使用して静的コンテンツの分離を行ってみました。
今回はJavaScriptを使用して、Webサーバ(flask)にアクセスしてみましょう。今回のソースコード
https://github.com/oq-Yuki-po/flask-tutorial/tree/chapter_006
フォルダ構成
. ├── README.md ├── client │ ├── css │ │ └── index.css │ ├── html │ │ └── index.html │ └── js │ └── index.js ├── docker-compose.yml ├── nginx │ ├── Dockerfile │ └── nginx.conf ├── postgresql │ └── init │ ├── 1_create_db.sql │ └── 2_create_table.sh ├── requirements.txt ├── src │ ├── UserModel.py │ ├── main.py │ └── setting.py └── test.http
イメージ図
投稿数が多くなってきたので、一旦整理してみます。
まず、サーバは全部で3つあります。
- nginx ・・・ htmlやcssなどの静的コンテンツの置き場所
- Flask ・・・ アプリケーションのCRUD処理を担当(データの保存や読み込み)
- PostgreSQL ・・・ DBとして使用
FlaskとPostgreSQLの繋ぎ役は
SQLAlchmy
にお願いしています。
今回の記事では、NginxとFlaskとの繋ぎ役の部分をJavaScript
で実装していきます。Flaskの準備
Flask側の準備からしていきましょう。
処理は簡単に、登録と取得の処理を実装します。
単純に、登録と取得の結果を返しているのみです。from flask import Flask, request from UserModel import User from setting import session from sqlalchemy import * from sqlalchemy.orm import * from flask_cors import CORS app = Flask(__name__) CORS(app) # 登録処理 @app.route('/', methods=["POST"]) def register_record(): name = request.form['name'] session.add(User(name)) session.commit() message = name + "の登録が完了しました!" return message # 取得処理 @app.route('/', methods=["GET"]) def fetch_record(): name = request.args.get('name') db_user = session.query(User.name).\ filter(User.name == name).\ all() if len(db_user) == 0: message = "は登録されていません。" else: message = "は登録されています。" return name + message if __name__ == '__main__': app.run()非同期通信の準備(JavaScript)
次にJavaScriptでの非同期通信の実装を行います。
最初にボタン押下時の関数(
check
とregister
)をそれぞれセットしています。ここでのポイントは
XMLHttpRequest
オブジェクトです。このオブジェクトは簡単に言うと非同期通を実現させる為に使用しています。
非同期通信を行うことで、ページ全体を再読み込みさせることなく部分的に更新させることが可能になります。
身近な例だとGoogleMapとかがそうです。
拡大や縮小をする度に、ページ全体が更新されてたら検索しにくいですよね??
下記の記事で詳しく説明してくれています。
https://qiita.com/hisamura333/items/e3ea6ae549eb09b7efb9
https://developer.mozilla.org/ja/docs/Web/API/XMLHttpRequest
document.getElementById('check').onclick = function () { check(); } document.getElementById('register').onclick = function () { register(); } function check() { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function () { if (this.readyState == 4 && this.status == 200) { document.getElementById('message').innerText = this.responseText; } }; const user_name = document.getElementById('user_name').value; // ここのURLはFlask起動時に表示されるURLにすること xhttp.open("GET", `http://127.0.0.1:5000/?name=${user_name}`, true); xhttp.send(); } function register() { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function () { if (this.readyState == 4 && this.status == 200) { document.getElementById('message').innerText = this.responseText; } }; const user_name = document.getElementById('user_name').value; xhttp.open("POST", "http://127.0.0.1:5000/", true); // POSTはURLにパラメータを載せないので、以下のようにやるよ xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhttp.send(`name=${user_name}`); }動作確認
ここまで準備ができたら
docker-compose up -dでコンテナを立ち上げて
falskも起動して、動作を確認してみよう
ブラウザ上から
http://localhost/
にアクセスQiita用の動画 pic.twitter.com/5YGsHE7QfP
— ペン太 (@oq_Yuki_op) September 29, 2019次回
- flaskをコンテナ化してみよう(uwsgiの利用)
- 投稿日:2019-09-29T12:33:39+09:00
PythonのフレームワークFlaskを使用してWebアプリ作成の物語(6)非同期通信でFlaskにアクセスしてみよう
目的
- 非同期通信でflaskに問い合わせをしてみよう
本編
前回まで
PythonのフレームワークFlaskを使用してWebアプリ作成の物語(5)nginxの登場
前回まではnginxを使用して静的コンテンツの分離を行ってみました。
今回はJavaScriptを使用して、Webサーバ(flask)にアクセスしてみましょう。今回のソースコード
https://github.com/oq-Yuki-po/flask-tutorial/tree/chapter_006
フォルダ構成
. ├── README.md ├── client │ ├── css │ │ └── index.css │ ├── html │ │ └── index.html │ └── js │ └── index.js ├── docker-compose.yml ├── nginx │ ├── Dockerfile │ └── nginx.conf ├── postgresql │ └── init │ ├── 1_create_db.sql │ └── 2_create_table.sh ├── requirements.txt ├── src │ ├── UserModel.py │ ├── main.py │ └── setting.py └── test.http
イメージ図
投稿数が多くなってきたので、一旦整理してみます。
まず、サーバは全部で3つあります。
- nginx ・・・ htmlやcssなどの静的コンテンツの置き場所
- Flask ・・・ アプリケーションのCRUD処理を担当(データの保存や読み込み)
- PostgreSQL ・・・ DBとして使用
FlaskとPostgreSQLの繋ぎ役は
SQLAlchmy
にお願いしています。
今回の記事では、NginxとFlaskとの繋ぎ役の部分をJavaScript
で実装していきます。Flaskの準備
Flask側の準備からしていきましょう。
処理は簡単に、登録と取得の処理を実装します。
単純に、登録と取得の結果を返しているのみです。from flask import Flask, request from UserModel import User from setting import session from sqlalchemy import * from sqlalchemy.orm import * from flask_cors import CORS app = Flask(__name__) CORS(app) # 登録処理 @app.route('/', methods=["POST"]) def register_record(): name = request.form['name'] session.add(User(name)) session.commit() message = name + "の登録が完了しました!" return message # 取得処理 @app.route('/', methods=["GET"]) def fetch_record(): name = request.args.get('name') db_user = session.query(User.name).\ filter(User.name == name).\ all() if len(db_user) == 0: message = "は登録されていません。" else: message = "は登録されています。" return name + message if __name__ == '__main__': app.run()非同期通信の準備(JavaScript)
次にJavaScriptでの非同期通信の実装を行います。
最初にボタン押下時の関数(
check
とregister
)をそれぞれセットしています。ここでのポイントは
XMLHttpRequest
オブジェクトです。このオブジェクトは簡単に言うと非同期通を実現させる為に使用しています。
非同期通信を行うことで、ページ全体を再読み込みさせることなく部分的に更新させることが可能になります。
身近な例だとGoogleMapとかがそうです。
拡大や縮小をする度に、ページ全体が更新されてたら検索しにくいですよね??
下記の記事で詳しく説明してくれています。
https://qiita.com/hisamura333/items/e3ea6ae549eb09b7efb9
https://developer.mozilla.org/ja/docs/Web/API/XMLHttpRequest
document.getElementById('check').onclick = function () { check(); } document.getElementById('register').onclick = function () { register(); } function check() { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function () { if (this.readyState == 4 && this.status == 200) { document.getElementById('message').innerText = this.responseText; } }; const user_name = document.getElementById('user_name').value; // ここのURLはFlask起動時に表示されるURLにすること xhttp.open("GET", `http://127.0.0.1:5000/?name=${user_name}`, true); xhttp.send(); } function register() { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function () { if (this.readyState == 4 && this.status == 200) { document.getElementById('message').innerText = this.responseText; } }; const user_name = document.getElementById('user_name').value; xhttp.open("POST", "http://127.0.0.1:5000/", true); // POSTはURLにパラメータを載せないので、以下のようにやるよ xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); xhttp.send(`name=${user_name}`); }動作確認
ここまで準備ができたら
docker-compose up -dでコンテナを立ち上げて
falskも起動して、動作を確認してみよう
ブラウザ上から
http://localhost/
にアクセスQiita用の動画 pic.twitter.com/5YGsHE7QfP
— ペン太 (@oq_Yuki_op) September 29, 2019次回
- flaskをコンテナ化してみよう(uwsgiの利用)
- 投稿日:2019-09-29T01:07:14+09:00
Google Authenticator のブラウザ版を作ってみた
はじめに
多要素認証(MFA)に使われる Google Authenticator や Microsoft Authenticator は、モバイル端末で使えるソフトウェアトークンだ。
ソフトウェアトークンとはワンタイムパスワード(使い捨てパスワード)を表示するアプリのことで、2段階認証アプリとも呼ばれる。
今回たまたま、2段階認証アプリのブラウザ動作版をサクっと作る機会があったので、その実装方法を解説する。例えば・・・従業員アカウントの秘密鍵(QRコードのotpauth URIにあるsecret)を情報システム部門で管理し、秘密鍵からワンタイムパスワードを表示する社内イントラのサイトを作れば、社内からしかログインできないシステムを容易に構築できるという寸法だ。
ワンタイムパスワードの計算方法
GoogleやMicrosoftの2段階認証アプリでは、次のワンタイムパスワード(以下OTPと略す)に対応している。
- HMAC-based One-Time Password (HOTP) RFC http://tools.ietf.org/html/rfc4226
- Time-based One-Time Password (TOTP) RFC http://tools.ietf.org/html/rfc6238
TOTPの計算方法(アルゴリズム)はRFCで公開されているから仕様書を読めば誰でも実装できる。RFC6238では
Java
の実装例も載っているので、Java
が読めれば他のプログラミング言語に移植するのも容易だろう。
TOTPは一定時間ごとに変化するHOTP(RFC4226)であり、ハッシュアルゴリズムにHMAC-SHA-1
を使っているからRFC2404の実装でもある。TOTPの仕組みはシンプルで、【サーバとクライアントで共有する秘密鍵】と【現在時刻(UNIX Time)をtime step(デフォルト30秒)で除したカウンター値】をSHA1でハッシュし、クライアントとサーバで一致するか確認しているだけだ。
ということで、秘密鍵から6桁のOTPを求めるコードを
PHP
で書くと、次のようになる。PHPfunction secret2otp($secret) { // 秘密鍵はBase32文字列で来るのでデコード $n = $bs = 0; $seed = ''; for ($i = 0; $i < strlen($secret); $i++) { $n <<= 5; $n += stripos('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', $secret[$i]); $bs = ($bs + 5) % 8; @$seed .= $bs < 5 ? chr(($n & (255 << $bs)) >> $bs) : null; } // UNIX Timeをtime stepで除したカウンタ値と鍵を合わせ、SHA1でハッシュ $hash = hash_hmac('sha1', str_pad(pack('N', intval(time() / 30)), 8, "\x00", STR_PAD_LEFT), $seed, false); // 下6桁を返す return sprintf('%06u', (hexdec(substr($hash, hexdec($hash[39]) * 2, 8)) & 0x7fffffff) % 1000000); }
PHP
にはBase32をデコードする関数は無いので、160ビット(32文字)の秘密鍵を最初にデコードする処理があるぶん長くなっているが、実処理がシンプルなのはお分かりいただけると思う。ブラウザで動かすのなら
JavaScript
で書く方法もあるが、秘密鍵をクライアント側に渡さないで済む方法を思いつかなかったので、サーバ側で処理することにした。実装方法
では、ブラウザで動かすための実装について具体的に解説していこう。
デモを用意したので、まずは見てもらいたい。もちろん設定された秘密鍵は適当である。
http://demo.mindwood.jp/WebAuthenticator.php?uid=example
IDから秘密鍵を求める
アカウントに相当するIDはGETパラメータで受け取ることにした。つまり、ID(デモでは "example")はURLに含まれる。
秘密鍵をハードコーディングしているが、実際には、CSVファイルやRDB、LDAP等のディレクトリサービスから秘密鍵を取得するのが現実的だろう。PHP$uid=$_GET['uid']; if($uid=='example') $sec='MINDWOOD2QIITA3SECRETKEY4DEMO567'; // 秘密鍵(もちろん架空) if($uid=='xxxxxxx') $sec='AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; if(is_null($sec)) { header('HTTP/1.1 404 Not Found'); exit; } $title="$uid のワンタイムパスワード"; // タイトルを設定未知のIDなら
404 Not Found
をブラウザに返す。なお、Apacheの
mod_rewrite
モジュールが許可された環境なら、パラメータ指定の動的なURLを、静的なURLにマッピングできる。動的URL(パラメータ渡し)http://example.com/WebAuthenticator.php?uid=example静的URL(ディレクトリ渡し)http://example.com/WebAuthenticator/example参考までに
mod_rewrite
のApache定義例を示す。htaccess
やAlias
を使っているため環境に応じて修正を要する。/var/www/html/auth/.htaccessRewriteEngine on RewriteBase /WebAuthenticator RewriteRule ^([0-9A-Za-z]+)$ WebAuthenticator.php?uid=$1/etc/httpd/conf.d/auth.confAlias /WebAuthenticator /var/www/html/auth/ <Directory "/var/www/html/auth"> Options -Indexes AllowOverride All Require all granted </Directory>HTMLコード
HTMLも至ってシンプルである。PHPタグで、冒頭の関数からOTPを算出し表示しているだけだ。
HTML<body> <h1><?php echo $title; ?></h1> <div id="code"> <span> <?php echo secret2otp($sec); ?><!-- OTPを表示 --> </span> </div> </body>以上で完成としても良いのだが、少し味気ないので、jQueryで肉付けする。
残り時間を表示させてみよう
OTPは30秒ごとに更新される。つまり使用期限がある。
30秒経つとOTPは変わってしまうため、ソフトウェアトークン同様、残り時間を画面に表示してみる。
ここでは、進捗度を表すサークルバー(プログレスサークル)のjQuery実装、jquery-circle-progressで飾る。まず、プログレスサークルの表示エリアをDIVタグで確保する。
HTML<div id="circle"> <strong></strong> </div>
setInterval
で1秒ごとに残り時間を更新する。JavaScriptsetInterval(function() { var now = new Date(); var left = 30 - (now.getSeconds() % 30); // 秒を30で割った余りを残り時間とする $('#code span').css('color', left < 6 ? 'red':'green'); // 残り5秒以下なら赤くする $('#circle strong').show(); $('#circle strong').fadeOut(900); // 文字をふわっと表示させるアニメーション効果 $('#circle strong').text(left); $('#circle').circleProgress({ // ここの書き方は公式ドキュメントを参照 startAngle: -1.55, reverse: true, value: left / 30, animationStartValue: (1 + left) / 30, size: 100, thickness: 16, fill: { gradient: ['#0681c4', '#07c6c1'] } }); if (left == 30) { location.reload(); } }, 1000);NTPで時刻同期している前提だが、もしWebサーバとクライアントの間でタイムラグがあるなら次のように調整すると良い。
JavaScriptnow.setSeconds(now.getSeconds() - 3); // 3秒ずらすなお、プログレスサークルを使って、サーバ側の進捗をリアルタイムに表示させる方法については、別記事に拙稿を書いているので興味のある人は参照して欲しい。
プログレスサークルをLaravelの非同期処理でリアルタイムに表示OTPをコピペできるようにしよう
クリップボードにOTPをコピーできると便利なので、コピーボタンを追加する。
HTML<div id="copy"> <button>COPY</button> </div>コピーボタンがクリックされたときの挙動を書く。
JavaScript$('#copy').on('click', function() { var clipboard = $('<textarea></textarea>'); clipboard.addClass('clipboard'); clipboard.html($('#code span').html().trim()); $(this).append(clipboard); clipboard.select(); // 選択させる document.execCommand('copy'); // 現在選択している部分をコピー clipboard.remove(); // イベント発火をユーザーに分かり易くするためアニメーション効果を入れる $('#code').addClass('animated rubberBand').one('animationend', function () { $('#code').removeClass('animated rubberBand'); }); });完成版
完成したソースコードはGistに置いた。
https://gist.github.com/mindwood2/5ad399b33a03c0cc47c1e7ef57194cd9