无密码验证可以让你只输入一个 email 而无需输入密码即可登入系统。这是一种比传统的电子邮件/密码验证方式登入更安全的方法。
下面我将为你展示,如何在 Go[1] 中实现一个 HTTP API 去提供这种服务。
流程
必需条件
从 Go 的主页[4] 上安装它,然后使用 go version
(1.10.1 atm)命令去检查它能否正常工作。
从 CockroachDB 的主页[5] 上下载它,展开它并添加到你的 PATH
变量中。使用 cockroach version
(2.0 atm)命令检查它能否正常工作。
数据库模式
现在,我们在 GOPATH
目录下为这个项目创建一个目录,然后使用 cockroach start
启动一个新的 CockroachDB 节点:
cockroach start --insecure --host 127.0.0.1
它会输出一些内容,找到 SQL 地址行,它将显示像 postgresql://root@127.0.0.1:26257?sslmode=disable
这样的内容。稍后我们将使用它去连接到数据库。
使用如下的内容去创建一个 schema.sql
文件。
DROP DATABASE IF EXISTS passwordless_demo CASCADE;
CREATE DATABASE IF NOT EXISTS passwordless_demo;
SET DATABASE = passwordless_demo;
CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email STRING UNIQUE,
username STRING UNIQUE
);
CREATE TABLE IF NOT EXISTS verification_codes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
INSERT INTO users (email, username) VALUES
('john@passwordless.local', 'john_doe');
这个脚本创建了一个名为 passwordless_demo
的数据库、两个名为 users
和 verification_codes
的表,以及为了稍后测试而插入的一些假用户。每个验证代码都与用户关联并保存创建时间,以用于去检查验证代码是否过期。
在另外的终端中使用 cockroach sql
命令去运行这个脚本:
cat schema.sql | cockroach sql --insecure
环境配置
需要配置两个环境变量:SMTP_USERNAME
和 SMTP_PASSWORD
,你可以从你的 mailtrap 帐户中获得它们。将在我们的程序中用到它们。
Go 依赖
我们需要下列的 Go 包:
go get -u github.com/lib/pq
go get -u github.com/matryer/way
go get -u github.com/dgrijalva/jwt-go
代码
初始化函数
创建 main.go
并且通过 init
函数里的环境变量中取得一些配置来启动。
var config struct {
port int
appURL *url.URL
databaseURL string
jwtKey []byte
smtpAddr string
smtpAuth smtp.Auth
}
func init() {
config.port, _ = strconv.Atoi(env("PORT", "80"))
config.appURL, _ = url.Parse(env("APP_URL", "http://localhost:"+strconv.Itoa(config.port)+"/"))
config.databaseURL = env("DATABASE_URL", "postgresql://root@127.0.0.1:26257/passwordless_demo?sslmode=disable")
config.jwtKey = []byte(env("JWT_KEY", "super-duper-secret-key"))
smtpHost := env("SMTP_HOST", "smtp.mailtrap.io")
config.smtpAddr = net.JoinHostPort(smtpHost, env("SMTP_PORT", "25"))
smtpUsername, ok := os.LookupEnv("SMTP_USERNAME")
if !ok {
log.Fatalln("could not find SMTP_USERNAME on environment variables")
}
smtpPassword, ok := os.LookupEnv("SMTP_PASSWORD")
if !ok {
log.Fatalln("could not find SMTP_PASSWORD on environment variables")
}
config.smtpAuth = smtp.PlainAuth("", smtpUsername, smtpPassword, smtpHost)
}
func env(key, fallbackValue string) string {
v, ok := os.LookupEnv(key)
if !ok {
return fallbackValue
}
return v
}
appURL
将去构建我们的 “魔法链接”。
port
将要启动的 HTTP 服务器。
databaseURL
是 CockroachDB 地址,我添加
/passwordless_demo
前面的数据库地址去表示数据库名字。
jwtKey
用于签名 JWT。
smtpAddr
是
SMTP_HOST
+
SMTP_PORT
的联合;我们将使用它去发送邮件。
smtpUsername
和
smtpPassword
是两个必需的变量。
smtpAuth
也是用于发送邮件。
env
函数允许我们去获得环境变量,不存在时返回一个回退值。
主函数
var db *sql.DB
func main() {
var err error
if db, err = sql.Open("postgres", config.databaseURL); err != nil {
log.Fatalf("could not open database connection: %v\n", err)
}
defer db.Close()
if err = db.Ping(); err != nil {
log.Fatalf("could not ping to database: %v\n", err)
}
router := way.NewRouter()
router.HandleFunc("POST", "/api/users", jsonRequired(createUser))
router.HandleFunc("POST", "/api/passwordless/start", jsonRequired(passwordlessStart))
router.HandleFunc("GET", "/api/passwordless/verify_redirect", passwordlessVerifyRedirect)
router.Handle("GET", "/api/auth_user", authRequired(getAuthUser))
addr := fmt.Sprintf(":%d", config.port)
log.Printf("starting server at %s