diff --git a/published/20180429 Passwordless Auth- Client.md b/published/20180429 Passwordless Auth- Client.md
new file mode 100644
index 0000000000..0a14e7a871
--- /dev/null
+++ b/published/20180429 Passwordless Auth- Client.md
@@ -0,0 +1,359 @@
+无密码验证:客户端
+======
+
+我们继续 [无密码验证][1] 的文章。上一篇文章中,我们用 Go 写了一个 HTTP 服务,用这个服务来做无密码验证 API。今天,我们为它再写一个 JavaScript 客户端。
+
+我们将使用 [这里的][2] 这个单页面应用程序(SPA)来展示使用的技术。如果你还没有读过它,请先读它。
+
+记住流程:
+
+- 用户输入其 email。
+- 用户收到一个带有魔法链接的邮件。
+- 用户点击该链接、
+- 用户验证成功。
+
+对于根 URL(`/`),我们将根据验证的状态分别使用两个不同的页面:一个是带有访问表单的页面,或者是已验证通过的用户的欢迎页面。另一个页面是验证回调的重定向页面。
+
+### 伺服
+
+我们将使用相同的 Go 服务器来为客户端提供服务,因此,在我们前面的 `main.go` 中添加一些路由:
+
+```
+router.Handle("GET", "/...", http.FileServer(SPAFileSystem{http.Dir("static")}))
+```
+
+```
+type SPAFileSystem struct {
+ fs http.FileSystem
+}
+
+func (spa SPAFileSystem) Open(name string) (http.File, error) {
+ f, err := spa.fs.Open(name)
+ if err != nil {
+ return spa.fs.Open("index.html")
+ }
+ return f, nil
+}
+```
+
+这个伺服文件放在 `static` 下,配合 `static/index.html` 作为回调。
+
+你可以使用你自己的服务器,但是你得在服务器上启用 [CORS][3]。
+
+### HTML
+
+我们来看一下那个 `static/index.html` 文件。
+
+```
+
+
+
+
+
+ Passwordless Demo
+
+
+
+
+
+```
+
+单页面应用程序的所有渲染由 JavaScript 来完成,因此,我们使用了一个空的 body 部分和一个 `main.js` 文件。
+
+我们将使用 [上篇文章][2] 中的 Router。
+
+### 渲染
+
+现在,我们使用下面的内容来创建一个 `static/js/main.js` 文件:
+
+```
+import Router from 'https://unpkg.com/@nicolasparada/router'
+import { isAuthenticated } from './auth.js'
+
+const router = new Router()
+
+router.handle('/', guard(view('home')))
+router.handle('/callback', view('callback'))
+router.handle(/^\//, view('not-found'))
+
+router.install(async resultPromise => {
+ document.body.innerHTML = ''
+ document.body.appendChild(await resultPromise)
+})
+
+function view(name) {
+ return (...args) => import(`/js/pages/${name}-page.js`)
+ .then(m => m.default(...args))
+}
+
+function guard(fn1, fn2 = view('welcome')) {
+ return (...args) => isAuthenticated()
+ ? fn1(...args)
+ : fn2(...args)
+}
+```
+
+与上篇文章不同的是,我们实现了一个 `isAuthenticated()` 函数和一个 `guard()` 函数,使用它去渲染两种验证状态的页面。因此,当用户访问 `/` 时,它将根据用户是否通过了验证来展示主页或者是欢迎页面。
+
+### 验证
+
+现在,我们来编写 `isAuthenticated()` 函数。使用下面的内容来创建一个 `static/js/auth.js` 文件:
+
+```
+export function getAuthUser() {
+ const authUserItem = localStorage.getItem('auth_user')
+ const expiresAtItem = localStorage.getItem('expires_at')
+
+ if (authUserItem !== null && expiresAtItem !== null) {
+ const expiresAt = new Date(expiresAtItem)
+
+ if (!isNaN(expiresAt.valueOf()) && expiresAt > new Date()) {
+ try {
+ return JSON.parse(authUserItem)
+ } catch (_) { }
+ }
+ }
+
+ return null
+}
+
+export function isAuthenticated() {
+ return localStorage.getItem('jwt') !== null && getAuthUser() !== null
+}
+```
+
+当有人登入时,我们将保存 JSON 格式的 web 令牌、它的过期日期,以及在 `localStorage` 上的当前已验证用户。这个模块就是这个用处。
+
+ * `getAuthUser()` 用于从 `localStorage` 获取已认证的用户,以确认 JSON 格式的 Web 令牌没有过期。
+ * `isAuthenticated()` 在前面的函数中用于去检查它是否没有返回 `null`。
+
+### 获取
+
+在继续这个页面之前,我将写一些与服务器 API 一起使用的 HTTP 工具。
+
+我们使用以下的内容去创建一个 `static/js/http.js` 文件:
+
+```
+import { isAuthenticated } from './auth.js'
+
+function get(url, headers) {
+ return fetch(url, {
+ headers: Object.assign(getAuthHeader(), headers),
+ }).then(handleResponse)
+}
+
+function post(url, body, headers) {
+ return fetch(url, {
+ method: 'POST',
+ headers: Object.assign(getAuthHeader(), { 'content-type': 'application/json' }, headers),
+ body: JSON.stringify(body),
+ }).then(handleResponse)
+}
+
+function getAuthHeader() {
+ return isAuthenticated()
+ ? { authorization: `Bearer ${localStorage.getItem('jwt')}` }
+ : {}
+}
+
+export async function handleResponse(res) {
+ const body = await res.clone().json().catch(() => res.text())
+ const response = {
+ statusCode: res.status,
+ statusText: res.statusText,
+ headers: res.headers,
+ body,
+ }
+ if (!res.ok) {
+ const message = typeof body === 'object' && body !== null && 'message' in body
+ ? body.message
+ : typeof body === 'string' && body !== ''
+ ? body
+ : res.statusText
+ const err = new Error(message)
+ throw Object.assign(err, response)
+ }
+ return response
+}
+
+export default {
+ get,
+ post,
+}
+```
+
+这个模块导出了 `get()` 和 `post()` 函数。它们是 `fetch` API 的封装。当用户是已验证的,这二个函数注入一个 `Authorization: Bearer ` 头到请求中;这样服务器就能对我们进行身份验证。
+
+### 欢迎页
+
+我们现在来到欢迎页面。用如下的内容创建一个 `static/js/pages/welcome-page.js` 文件:
+
+```
+const template = document.createElement('template')
+template.innerHTML = `
+ Passwordless Demo
+ Access
+
+`
+
+export default function welcomePage() {
+ const page = template.content.cloneNode(true)
+
+ page.getElementById('access-form')
+ .addEventListener('submit', onAccessFormSubmit)
+
+ return page
+}
+```
+
+这个页面使用一个 `HTMLTemplateElement` 作为视图。这只是一个输入用户 email 的简单表单。
+
+为了避免干扰,我将跳过错误处理部分,只是将它们输出到控制台上。
+
+现在,我们来写 `onAccessFormSubmit()` 函数。
+
+```
+import http from '../http.js'
+
+function onAccessFormSubmit(ev) {
+ ev.preventDefault()
+
+ const form = ev.currentTarget
+ const input = form.querySelector('input')
+ const email = input.value
+
+ sendMagicLink(email).catch(err => {
+ console.error(err)
+ if (err.statusCode === 404 && wantToCreateAccount()) {
+ runCreateUserProgram(email)
+ }
+ })
+}
+
+function sendMagicLink(email) {
+ return http.post('/api/passwordless/start', {
+ email,
+ redirectUri: location.origin + '/callback',
+ }).then(() => {
+ alert('Magic link sent. Go check your email inbox.')
+ })
+}
+
+function wantToCreateAccount() {
+ return prompt('No user found. Do you want to create an account?')
+}
+```
+
+它对 `/api/passwordless/start` 发起了 POST 请求,请求体中包含 `email` 和 `redirectUri`。在本例中它返回 `404 Not Found` 状态码时,我们将创建一个用户。
+
+```
+function runCreateUserProgram(email) {
+ const username = prompt("Enter username")
+ if (username === null) return
+
+ http.post('/api/users', { email, username })
+ .then(res => res.body)
+ .then(user => sendMagicLink(user.email))
+ .catch(console.error)
+}
+```
+
+这个用户创建程序,首先询问用户名,然后使用 email 和用户名做一个 `POST` 请求到 `/api/users`。成功之后,给创建的用户发送一个魔法链接。
+
+### 回调页
+
+这是访问表单的全部功能,现在我们来做回调页面。使用如下的内容来创建一个 `static/js/pages/callback-page.js` 文件:
+
+```
+import http from '../http.js'
+
+const template = document.createElement('template')
+template.innerHTML = `
+ Authenticating you
+`
+
+export default function callbackPage() {
+ const page = template.content.cloneNode(true)
+
+ const hash = location.hash.substr(1)
+ const fragment = new URLSearchParams(hash)
+ for (const [k, v] of fragment.entries()) {
+ fragment.set(decodeURIComponent(k), decodeURIComponent(v))
+ }
+ const jwt = fragment.get('jwt')
+ const expiresAt = fragment.get('expires_at')
+
+ http.get('/api/auth_user', { authorization: `Bearer ${jwt}` })
+ .then(res => res.body)
+ .then(authUser => {
+ localStorage.setItem('jwt', jwt)
+ localStorage.setItem('auth_user', JSON.stringify(authUser))
+ localStorage.setItem('expires_at', expiresAt)
+
+ location.replace('/')
+ })
+ .catch(console.error)
+
+ return page
+}
+```
+
+请记住……当点击魔法链接时,我们会来到 `/api/passwordless/verify_redirect`,它将把我们重定向到重定向 URI,我们将放在哈希中的 JWT 和过期日期传递给 `/callback`。
+
+回调页面解码 URL 中的哈希,提取这些参数去做一个 `GET` 请求到 `/api/auth_user`,用 JWT 保存所有数据到 `localStorage` 中。最后,重定向到主页面。
+
+### 主页
+
+创建如下内容的 `static/pages/home-page.js` 文件:
+
+```
+import { getAuthUser } from '../auth.js'
+
+export default function homePage() {
+ const authUser = getAuthUser()
+
+ const template = document.createElement('template')
+ template.innerHTML = `
+ Passwordless Demo
+ Welcome back, ${authUser.username} 👋
+
+ `
+
+ const page = template.content
+
+ page.getElementById('logout-button')
+ .addEventListener('click', logout)
+
+ return page
+}
+
+function logout() {
+ localStorage.clear()
+ location.reload()
+}
+```
+
+这个页面用于欢迎已验证用户,同时也有一个登出按钮。`logout()` 函数的功能只是清理掉 `localStorage` 并重载这个页面。
+
+这就是全部内容了。我猜你在此之前已经看过这个 [demo][4] 了。当然,这些源代码也在同一个 [仓库][5] 中。
+
+--------------------------------------------------------------------------------
+
+via: https://nicolasparada.netlify.com/posts/passwordless-auth-client/
+
+作者:[Nicolás Parada][a]
+选题:[lujun9972](https://github.com/lujun9972)
+译者:[qhwdw](https://github.com/qhwdw)
+校对:[wxy](https://github.com/wxy)
+
+本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
+
+[a]:https://nicolasparada.netlify.com/
+[1]:https://linux.cn/article-9748-1.html
+[2]:https://linux.cn/article-9815-1.html
+[3]:https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
+[4]:https://go-passwordless-demo.herokuapp.com/
+[5]:https://github.com/nicolasparada/go-passwordless-demo
diff --git a/translated/tech/20180429 Passwordless Auth- Client.md b/translated/tech/20180429 Passwordless Auth- Client.md
deleted file mode 100644
index e2faf2cb6e..0000000000
--- a/translated/tech/20180429 Passwordless Auth- Client.md
+++ /dev/null
@@ -1,338 +0,0 @@
-无密码验证:客户端
-======
-我们继续 [无密码验证][1] 的文章。上一篇文章中,我们用 Go 写了一个 HTTP 服务,用这个服务来做无密码验证 API。今天,我们为它再写一个 JavaScript 客户端。
-
-我们将使用 [这里的][2] 这个单页面应用程序(SPA)来展示使用的技术。如果你还没有读过它,请先读它。
-
-我们将根据验证的状态分别使用两个不同的根 URL(`/`):一个是访问状态的页面或者是欢迎已验证用户的页面。另一个页面是验证失败后重定向到验证页面。
-
-### Serving
-
-我们将使用相同的 Go 服务器来为客户端提供服务,因此,在我们前面的 `main.go` 中添加一些路由:
-```
-router.Handle("GET", "/js/", http.FileServer(http.Dir("static")))
-router.HandleFunc("GET", "/...", serveFile("static/index.html"))
-
-```
-
-这个伺服文件在 `static/js` 下,而 `static/index.html` 文件是为所有的访问提供服务的。
-
-你可以使用你自己的服务器,但是你得在服务器上启用 [CORS][3]。
-
-### HTML
-
-我们来看一下那个 `static/index.html` 文件。
-```
-
-
-
-
-
- Passwordless Demo
-
-
-
-
-
-
-```
-
-单页面应用程序剩余的渲染由 JavaScript 来完成,因此,我们使用了一个空的 body 部分和一个 `main.js` 文件。
-
-我们将使用 [上篇文章][2] 中的 Router。
-
-### Rendering
-
-现在,我们使用下面的内容来创建一个 `static/js/main.js` 文件:
-```
-import Router from 'https://unpkg.com/@nicolasparada/router'
-import { isAuthenticated } from './auth.js'
-
-const router = new Router()
-
-router.handle('/', guard(view('home')))
-router.handle('/callback', view('callback'))
-router.handle(/^\//, view('not-found'))
-
-router.install(async resultPromise => {
- document.body.innerHTML = ''
- document.body.appendChild(await resultPromise)
-})
-
-function view(name) {
- return (...args) => import(`/js/pages/${name}-page.js`)
- .then(m => m.default(...args))
-}
-
-function guard(fn1, fn2 = view('welcome')) {
- return (...args) => isAuthenticated()
- ? fn1(...args)
- : fn2(...args)
-}
-
-```
-
-与上篇文章不同的是,我们实现了一个 `isAuthenticated()` 函数和一个 `guard()` 函数,使用它去渲染两种验证状态的页面。因此,当用户访问 `/` 时,它将根据用户是否通过了验证来展示 home 页面或者是欢迎页面。
-
-### Auth
-
-现在,我们来编写 `isAuthenticated()` 函数。使用下面的内容来创建一个 `static/js/auth.js` 文件:
-```
-export function getAuthUser() {
- const authUserItem = localStorage.getItem('auth_user')
- const expiresAtItem = localStorage.getItem('expires_at')
-
- if (authUserItem !== null && expiresAtItem !== null) {
- const expiresAt = new Date(expiresAtItem)
-
- if (!isNaN(expiresAt.valueOf()) && expiresAt > new Date()) {
- try {
- return JSON.parse(authUserItem)
- } catch (_) { }
- }
- }
-
- return null
-}
-
-export function isAuthenticated() {
- return localStorage.getItem('jwt') !== null && getAuthUser() !== null
-}
-
-```
-
-当有人登入时,我们将保存 JSON 格式的 web 令牌、过期日期、以及在 `localStorage` 上的当前已验证用户。这个模块就是这个用处。
-
- * `getAuthUser()` 用于从 `localStorage` 获取已认证的用户,以确认 JSON 格式的 Web 令牌没有过期。
- * `isAuthenticated()` 在前面的函数中用于去检查它是否返回了 `null`。
-
-
-
-### Fetch
-
-在继续这个页面之前,我将写一些与服务器 API 一起使用的 HTTP 工具。
-
-我们使用以下的内容去创建一个 `static/js/http.js` 文件:
-```
-import { isAuthenticated } from './auth.js'
-
-function get(url, headers) {
- return fetch(url, {
- headers: Object.assign(getAuthHeader(), headers),
- }).then(handleResponse)
-}
-
-function post(url, body, headers) {
- return fetch(url, {
- method: 'POST',
- headers: Object.assign(getAuthHeader(), { 'content-type': 'application/json' }, headers),
- body: JSON.stringify(body),
- }).then(handleResponse)
-}
-
-function getAuthHeader() {
- return isAuthenticated()
- ? { authorization: `Bearer ${localStorage.getItem('jwt')}` }
- : {}
-}
-
-export async function handleResponse(res) {
- const body = await res.clone().json().catch(() => res.text())
- const response = {
- url: res.url,
- statusCode: res.status,
- statusText: res.statusText,
- headers: res.headers,
- body,
- }
- if (!res.ok) throw Object.assign(
- new Error(body.message || body || res.statusText),
- response
- )
- return response
-}
-
-export default {
- get,
- post,
-}
-
-```
-
-这个模块导出了 `get()` 和 `post()` 函数。它们是 `fetch` API 的封装。当用户是已验证的,这二个函数注入一个 `Authorization: Bearer ` 头到请求中;这样服务器就能对我们进行身份验证。
-
-### Welcome Page
-
-我们现在来到欢迎页面。用如下的内容创建一个 `static/js/pages/welcome-page.js` 文件:
-```
-const template = document.createElement('template')
-template.innerHTML = `
- Passwordless Demo
- Access
-
-`
-
-export default function welcomePage() {
- const page = template.content.cloneNode(true)
-
- page.getElementById('access-form')
- .addEventListener('submit', onAccessFormSubmit)
-
- return page
-}
-
-```
-
-正如你所见,这个页面使用一个 `HTMLTemplateElement`。这是一个只输入用户 email 的简单表格。
-
-为了不让代码太乏味,我将跳过错误处理部分,只是将它们输出到控制台上。
-
-现在,我们来写 `onAccessFormSubmit()` 函数。
-```
-import http from '../http.js'
-
-function onAccessFormSubmit(ev) {
- ev.preventDefault()
-
- const form = ev.currentTarget
- const input = form.querySelector('input')
- const email = input.value
-
- sendMagicLink(email).catch(err => {
- console.error(err)
- if (err.statusCode === 404 && wantToCreateAccount()) {
- runCreateUserProgram(email)
- }
- })
-}
-
-function sendMagicLink(email) {
- return http.post('/api/passwordless/start', {
- email,
- redirectUri: location.origin + '/callback',
- }).then(() => {
- alert('Magic link sent. Go check your email inbox.')
- })
-}
-
-function wantToCreateAccount() {
- return prompt('No user found. Do you want to create an account?')
-}
-
-```
-
-它使用 email 做了一个 `POST` 请求到 `/api/passwordless/start`,然后在 body 中做了 URI 转向。在本例中使用 `404 Not Found` 状态码返回,我们将创建一个用户。
-```
-function runCreateUserProgram(email) {
- const username = prompt("Enter username")
- if (username === null) return
-
- http.post('/api/users', { email, username })
- .then(res => res.body)
- .then(user => sendMagicLink(user.email))
- .catch(console.error)
-}
-
-```
-
-这个用户创建程序,首先询问用户名,然后使用 email 和用户名,在 body 中做一个 `POST` 请求到 `/api/users`。成功之后,给创建的用户发送一个魔法链接。
-
-### Callback Page
-
-这就是访问表格的所有功能,现在我们来做回调页面。使用如下的内容来创建一个 `static/js/pages/callback-page.js` 文件:
-```
-import http from '../http.js'
-
-const template = document.createElement('template')
-template.innerHTML = `
- Authenticating you 👀
-`
-
-export default function callbackPage() {
- const page = template.content.cloneNode(true)
-
- const hash = location.hash.substr(1)
- const fragment = new URLSearchParams(hash)
- for (const [k, v] of fragment.entries()) {
- fragment.set(decodeURIComponent(k), decodeURIComponent(v))
- }
- const jwt = fragment.get('jwt')
- const expiresAt = fragment.get('expires_at')
-
- http.get('/api/auth_user', { authorization: `Bearer ${jwt}` })
- .then(res => res.body)
- .then(authUser => {
- localStorage.setItem('jwt', jwt)
- localStorage.setItem('auth_user', JSON.stringify(authUser))
- localStorage.setItem('expires_at', expiresAt)
-
- location.replace('/')
- })
- .catch(console.error)
-
- return page
-}
-
-```
-
-请记住 … 当点击魔法链接时,我们来到 `/api/passwordless/verify_redirect`,我们通过 (`/callback`)在 URL 的哈希中传递 JWT 和过期日期,将我们转向到重定向 URI。
-
-回调页面解码 URL 中的哈希,提取这些参数去做一个 `GET` 请求到 `/api/auth_user`,用 JWT 保存所有数据到 `localStorage` 中。最后,重定向到主页面。
-
-### Home Page
-
-创建如下内容的 `static/pages/home-page.js` 文件:
-```
-import { getAuthUser } from '../auth.js'
-
-export default function homePage() {
- const authUser = getAuthUser()
-
- const template = document.createElement('template')
- template.innerHTML = `
- Passwordless Demo
- Welcome back, ${authUser.username} 👋
-
- `
-
- const page = template.content
-
- page.getElementById('logout-button')
- .addEventListener('click', logout)
-
- return page
-}
-
-function logout() {
- localStorage.clear()
- location.reload()
-}
-
-```
-
-这个页面欢迎已验证用户,同时也有一个登出按钮。`logout()` 函数的功能只是清理掉 `localStorage` 并重载这个页面。
-
-这就是全部内容了。我敢说你在此之前已经看过这个 [demo][4] 了。当然,这些源代码也在同一个 [仓库][5] 中。
-
-👋👋👋
-
---------------------------------------------------------------------------------
-
-via: https://nicolasparada.netlify.com/posts/passwordless-auth-client/
-
-作者:[Nicolás Parada][a]
-选题:[lujun9972](https://github.com/lujun9972)
-译者:[qhwdw](https://github.com/qhwdw)
-校对:[校对者ID](https://github.com/校对者ID)
-
-本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
-
-[a]:https://nicolasparada.netlify.com/
-[1]:https://nicolasparada.netlify.com/posts/passwordless-auth-server/
-[2]:https://nicolasparada.netlify.com/posts/javascript-client-router/
-[3]:https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
-[4]:https://go-passwordless-demo.herokuapp.com/
-[5]:https://github.com/nicolasparada/go-passwordless-demo