SMS login
parent
01848e9493
commit
c6c1d0d160
6
.env
6
.env
|
@ -1,2 +1,8 @@
|
||||||
LISTEN=:8080
|
LISTEN=:8080
|
||||||
SECRET=secret
|
SECRET=secret
|
||||||
|
POSTGRES=postgresql://root@localhost:26257/core?sslmode=disable
|
||||||
|
REDIS=:6379
|
||||||
|
TTL=120s
|
||||||
|
MESSAGING_SID=MG19d18fafcff1f3f34dff04c5b04c0699
|
||||||
|
TWILIO_SID=AC22ea3eea85e5108a96b947aea8ab1320
|
||||||
|
TWILIO_TOKEN=fb23fa1a1564aa9f62a7a3117f07b3a0
|
||||||
|
|
82
README.md
82
README.md
|
@ -1,6 +1,6 @@
|
||||||
# backend-login
|
# backend-login
|
||||||
|
|
||||||
Beep backend handling login. For now, just a POST endpoint returning a JWT. In the furture, SMS-based perpetual login.
|
Beep backend handling login. Call `/init` and then `/verify` in sequence. `/login` is legacy to provide an easy source of tokens for testing, and will be removed someday™.
|
||||||
|
|
||||||
## Environment variables
|
## Environment variables
|
||||||
|
|
||||||
|
@ -11,24 +11,92 @@ Supply environment variables by either exporting them or editing ```.env```.
|
||||||
| LISTEN | Host and port number to listen on | :8080 |
|
| LISTEN | Host and port number to listen on | :8080 |
|
||||||
| SECRET | JWT secret | secret |
|
| SECRET | JWT secret | secret |
|
||||||
|
|
||||||
## API (temporary)
|
## API
|
||||||
|
|
||||||
|
### Init Auth
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /init
|
||||||
|
```
|
||||||
|
|
||||||
|
Kick off SMS verification process.
|
||||||
|
|
||||||
|
#### Body
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---- | ---- | ----------- |
|
||||||
|
| phone_number | String | Verifying phone number in format `<country code><8 digits>`. |
|
||||||
|
|
||||||
|
#### Success (200 OK)
|
||||||
|
|
||||||
|
A nonce, to be used for `/verify` to add additional entropy.
|
||||||
|
|
||||||
|
#### Errors
|
||||||
|
|
||||||
|
| Code | Description |
|
||||||
|
| ---- | ----------- |
|
||||||
|
| 400 | Error parsing body/phone_number is not a valid phone number |
|
||||||
|
| 500 | Error generating nonce/Making request to Twilio SMS |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Verify Code
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /verify
|
||||||
|
```
|
||||||
|
|
||||||
|
Second half of the verification process, verifying the code and returning a JWT. If the user does not exist in the database, a blank one is created.
|
||||||
|
|
||||||
|
#### Body
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---- | ---- | ----------- |
|
||||||
|
| code | String | Verification code received by SMS. |
|
||||||
|
| nonce | String | Nonce returned by `/init`. |
|
||||||
|
| clientid | String | ID unique to device, e.g. MAC Address |
|
||||||
|
|
||||||
|
#### Success (200 OK)
|
||||||
|
|
||||||
|
JWT token.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"userid": "<userid>",
|
||||||
|
"clientid": "<clientid>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Errors
|
||||||
|
|
||||||
|
| Code | Description |
|
||||||
|
| ---- | ----------- |
|
||||||
|
| 400 | Error parsing body |
|
||||||
|
| 404 | Code with nonce supplied was not found |
|
||||||
|
| 500 | Error retrieving record from Redis/querying postgres/creating user ID/generating token |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Create Token (temporary)
|
||||||
|
|
||||||
```
|
```
|
||||||
POST /login
|
POST /login
|
||||||
```
|
```
|
||||||
|
|
||||||
### Body
|
Just a simple little endpoint to get a valid token without having to jump through the (expensive) hoops of SMS Authentication.
|
||||||
|
|
||||||
|
#### Body
|
||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
| ---- | ---- | ----------- | -------- |
|
| ---- | ---- | ----------- | -------- |
|
||||||
| user | String | User's ID. | ✓ |
|
| userid | String | User's ID. | ✓ |
|
||||||
| device | String | Device's ID. Must be unique to the device. I suggest something based on MAC address. | ✓ |
|
| clientid | String | Device's ID. Must be unique to the device. I suggest something based on MAC address. | ✓ |
|
||||||
|
|
||||||
### Success (200 OK)
|
#### Success (200 OK)
|
||||||
|
|
||||||
JWT token.
|
JWT token.
|
||||||
|
|
||||||
### Errors
|
#### Errors
|
||||||
|
|
||||||
| Code | Description |
|
| Code | Description |
|
||||||
| ---- | ----------- |
|
| ---- | ----------- |
|
||||||
|
|
7
go.mod
7
go.mod
|
@ -2,6 +2,13 @@ module login
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||||
|
github.com/go-redis/redis v6.15.1+incompatible
|
||||||
github.com/joho/godotenv v1.3.0
|
github.com/joho/godotenv v1.3.0
|
||||||
github.com/julienschmidt/httprouter v1.2.0
|
github.com/julienschmidt/httprouter v1.2.0
|
||||||
|
github.com/lib/pq v1.0.0
|
||||||
|
github.com/onsi/ginkgo v1.7.0 // indirect
|
||||||
|
github.com/onsi/gomega v1.4.3 // indirect
|
||||||
|
github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 // indirect
|
||||||
|
github.com/ttacon/libphonenumber v1.0.1
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 // indirect
|
||||||
)
|
)
|
||||||
|
|
36
go.sum
36
go.sum
|
@ -1,6 +1,42 @@
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
|
github.com/go-redis/redis v6.15.1+incompatible h1:BZ9s4/vHrIqwOb0OPtTQ5uABxETJ3NRuUNoSUurnkew=
|
||||||
|
github.com/go-redis/redis v6.15.1+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||||
|
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||||
github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
|
github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
|
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||||
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||||
|
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
|
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||||
|
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||||
|
github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 h1:5u+EJUQiosu3JFX0XS0qTf5FznsMOzTjGqavBGuCbo0=
|
||||||
|
github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2/go.mod h1:4kyMkleCiLkgY6z8gK5BkI01ChBtxR0ro3I1ZDcGM3w=
|
||||||
|
github.com/ttacon/libphonenumber v1.0.1 h1:sYxYtW16xbklwUA3tJjTGMInEMLYClJjiIX4b7t5Ip0=
|
||||||
|
github.com/ttacon/libphonenumber v1.0.1/go.mod h1:E0TpmdVMq5dyVlQ7oenAkhsLu86OkUl+yR4OAxyEg/M=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
|
||||||
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
|
||||||
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||||
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||||
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
210
main.go
210
main.go
|
@ -1,18 +1,40 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/tls"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
"github.com/dgrijalva/jwt-go"
|
"github.com/dgrijalva/jwt-go"
|
||||||
|
"github.com/go-redis/redis"
|
||||||
|
"github.com/ttacon/libphonenumber"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
)
|
)
|
||||||
|
|
||||||
var listen string
|
var listen string
|
||||||
|
var postgres string
|
||||||
|
var redisHost string
|
||||||
var secret []byte
|
var secret []byte
|
||||||
|
var ttl time.Duration
|
||||||
|
var messagingSID string
|
||||||
|
|
||||||
|
var twilioSID string
|
||||||
|
var twilioToken string
|
||||||
|
|
||||||
|
var db *sql.DB
|
||||||
|
var redisClient *redis.Client
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Load .env
|
// Load .env
|
||||||
|
@ -21,20 +43,204 @@ func main() {
|
||||||
log.Fatal("Error loading .env file")
|
log.Fatal("Error loading .env file")
|
||||||
}
|
}
|
||||||
listen = os.Getenv("LISTEN")
|
listen = os.Getenv("LISTEN")
|
||||||
s := os.Getenv("SECRET")
|
secret = []byte(os.Getenv("SECRET"))
|
||||||
|
postgres = os.Getenv("POSTGRES")
|
||||||
|
redisHost = os.Getenv("REDIS")
|
||||||
|
|
||||||
secret = []byte(s)
|
ttl, err = time.ParseDuration(os.Getenv("TTL"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error parsing ttl")
|
||||||
|
}
|
||||||
|
|
||||||
|
messagingSID = os.Getenv("MESSAGING_SID")
|
||||||
|
twilioSID = os.Getenv("TWILIO_SID")
|
||||||
|
twilioToken = os.Getenv("TWILIO_TOKEN")
|
||||||
|
|
||||||
|
// Postgres
|
||||||
|
log.Printf("connecting to postgres %s", postgres)
|
||||||
|
db, err = sql.Open("postgres", postgres)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Redis
|
||||||
|
redisClient = redis.NewClient(&redis.Options{
|
||||||
|
Addr: redisHost,
|
||||||
|
Password: "",
|
||||||
|
DB: 1,
|
||||||
|
})
|
||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
router := httprouter.New()
|
router := httprouter.New()
|
||||||
|
|
||||||
router.POST("/login", Login);
|
router.POST("/login", Login);
|
||||||
|
router.POST("/init", InitRequest)
|
||||||
|
router.POST("/verify", VerifyCode)
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
log.Printf("starting server on %s", listen)
|
log.Printf("starting server on %s", listen)
|
||||||
log.Fatal(http.ListenAndServe(listen, router))
|
log.Fatal(http.ListenAndServe(listen, router))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParsePhone(phone string) (string, error) {
|
||||||
|
num, err := libphonenumber.Parse(phone, "")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return libphonenumber.Format(num, libphonenumber.INTERNATIONAL), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func RandomHex() (string, error) {
|
||||||
|
b := make([]byte, 16)
|
||||||
|
_, err := rand.Read(b)
|
||||||
|
return hex.EncodeToString(b), err
|
||||||
|
}
|
||||||
|
|
||||||
|
type InitRequestBody struct {
|
||||||
|
PhoneNumber string `json:"phone_number"`
|
||||||
|
}
|
||||||
|
func InitRequest(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||||
|
// Get request body
|
||||||
|
req := InitRequestBody{}
|
||||||
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
err := decoder.Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure phone number is legitimate
|
||||||
|
phone, err := ParsePhone(req.PhoneNumber)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate OTP code
|
||||||
|
c, err := rand.Int(rand.Reader, big.NewInt(1000000))
|
||||||
|
code := fmt.Sprintf("%06d", c)
|
||||||
|
|
||||||
|
// Generate nonce
|
||||||
|
b := make([]byte, 16)
|
||||||
|
_, err = rand.Read(b)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bytes := hex.EncodeToString(b)
|
||||||
|
|
||||||
|
// Set code-nonce pair in redis first
|
||||||
|
redisClient.Set(code + "nonce", bytes, ttl)
|
||||||
|
// Set code-phone_number pair
|
||||||
|
redisClient.Set(code + "phone", phone, ttl)
|
||||||
|
|
||||||
|
// Send SMS via Twilio
|
||||||
|
data := url.Values {}
|
||||||
|
data.Set("MessagingServiceSid", messagingSID)
|
||||||
|
data.Set("To", phone)
|
||||||
|
data.Set("Body", fmt.Sprintf("Your OTP for Beep is %s", code))
|
||||||
|
|
||||||
|
url := fmt.Sprintf("https://api.twilio.com/2010-04-01/Accounts/%s/Messages.json", twilioSID)
|
||||||
|
twilioReq, err := http.NewRequest("POST", url, strings.NewReader(data.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
twilioReq.SetBasicAuth(twilioSID, twilioToken)
|
||||||
|
twilioReq.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
// Twilio uses self-signed certs
|
||||||
|
transport := &http.Transport {
|
||||||
|
TLSClientConfig: &tls.Config{ InsecureSkipVerify: true },
|
||||||
|
}
|
||||||
|
client := &http.Client{ Transport: transport }
|
||||||
|
resp, err := client.Do(twilioReq)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return nonce
|
||||||
|
w.Write([]byte(bytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
type VerifyRequestBody struct {
|
||||||
|
Code string `json:"code"`
|
||||||
|
Nonce string `json:"nonce"`
|
||||||
|
ClientId string `json:"clientid"`
|
||||||
|
}
|
||||||
|
func VerifyCode(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
|
||||||
|
// Get request body
|
||||||
|
req := VerifyRequestBody{}
|
||||||
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
err := decoder.Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get nonce
|
||||||
|
storedNonce, err := redisClient.Get(req.Code + "nonce").Result()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Nonce != storedNonce {
|
||||||
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get stored phone number
|
||||||
|
phoneNumber, err := redisClient.Get(req.Code + "phone").Result()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate (potential) User ID
|
||||||
|
userHex, err := RandomHex()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userIDPotential := "u-" + userHex
|
||||||
|
|
||||||
|
// Check for existing user
|
||||||
|
var userID string
|
||||||
|
err = db.QueryRow(`
|
||||||
|
INSERT INTO "user" (id, first_name, last_name, phone_number)
|
||||||
|
VALUES ($1, '', '', $2)
|
||||||
|
ON CONFLICT(phone_number)
|
||||||
|
DO UPDATE SET phone_number=EXCLUDED.phone_number
|
||||||
|
RETURNING id
|
||||||
|
`, userIDPotential, phoneNumber).Scan(&userID)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate JWT
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims {
|
||||||
|
"userid": userID,
|
||||||
|
"clientid": req.ClientId,
|
||||||
|
})
|
||||||
|
|
||||||
|
tokenString, err := token.SignedString(secret)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(tokenString))
|
||||||
|
}
|
||||||
|
|
||||||
type LoginData struct {
|
type LoginData struct {
|
||||||
ID string `json:"userid"`
|
ID string `json:"userid"`
|
||||||
Client string `json:"clientid"`
|
Client string `json:"clientid"`
|
||||||
|
|
Loading…
Reference in New Issue