-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
加上使用 Google Authenticator 的認證機制 使用 git 應該培養出時常開 branch 的習慣,開發完成再 merge --no-ff 回來, 這樣你可以在 commit log 寫下這整條分支到底做了什麼,日後要查的時候會方便 非常多。 合回來之前最好也把分支名稱改成比較有意義的名字,那你就可以用 git log | grep -A 5 keyword 這種方式來快速找到特定的開發歷程。
- Loading branch information
Showing
16 changed files
with
505 additions
and
91 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
|
||
"github.com/Patrolavia/jsonapi" | ||
) | ||
|
||
type auth struct { | ||
A Authenticator | ||
} | ||
|
||
// 錯誤處理會重複使用,抽出來 | ||
func (h *auth) e(enc *json.Encoder, httpData *jsonapi.HTTP, msg string) { | ||
httpData.WriteHeader(http.StatusBadRequest) | ||
enc.Encode(msg) | ||
return | ||
} | ||
|
||
func (h *auth) Handle(enc *json.Encoder, dec *json.Decoder, httpData *jsonapi.HTTP) { | ||
// 定義參數型別 | ||
type p struct { | ||
Pin string `json:"pin"` | ||
} | ||
|
||
var param p | ||
|
||
// 同樣的錯誤會在這個方法裡重複使用,所以拉出來 | ||
|
||
if err := dec.Decode(¶m); err != nil { | ||
h.e(enc, httpData, "Parameter is not Pin object") | ||
return | ||
} | ||
|
||
// validating data | ||
token, err := h.A.Token(param.Pin) | ||
if err != nil { | ||
h.e(enc, httpData, "Pin code incorrect") | ||
return | ||
} | ||
|
||
enc.Encode(token) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package main | ||
|
||
import ( | ||
"net/http" | ||
"testing" | ||
|
||
"github.com/Patrolavia/jsonapi" | ||
) | ||
|
||
func TestAuthOK(t *testing.T) { | ||
fake := FakeAuthenticator("123456") | ||
h := &auth{fake} | ||
|
||
resp, err := jsonapi.HandlerTest(h.Handle).Post("/api/auth", "", `{"pin":"123456"}`) | ||
|
||
if err != nil { | ||
t.Fatalf("unexpected error occured when testing auth: %s", err) | ||
} | ||
|
||
if resp.Code != http.StatusOK { | ||
t.Fatalf("auth: sent correct pin but got http status %d", resp.Code) | ||
} | ||
|
||
if resp.Body == nil { | ||
t.Fatal("auth: got 200 OK, but no token") | ||
} | ||
} | ||
|
||
func TestAuthWrongPin(t *testing.T) { | ||
fake := FakeAuthenticator("123456") | ||
h := &auth{fake} | ||
|
||
cases := []struct { | ||
in string | ||
msg string | ||
}{ | ||
{`{"pin":"654321"}`, "auth: sent wrong pin, expect 400, got %d"}, | ||
{`{}`, "auth: sent no pin, expect 400, got %d"}, | ||
{`"123456"`, "auth: sent wrong format, expect 400, got %d"}, | ||
} | ||
|
||
for _, c := range cases { | ||
resp, err := jsonapi.HandlerTest(h.Handle).Post("/api/auth", "", c.in) | ||
|
||
if err != nil { | ||
t.Fatalf("unexpected error occured when testing auth: %s", err) | ||
} | ||
|
||
if resp.Code != http.StatusBadRequest { | ||
t.Errorf(c.msg, resp.Code) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package main | ||
|
||
import ( | ||
"math/rand" | ||
"strconv" | ||
"sync" | ||
|
||
"github.com/dgryski/dgoogauth" | ||
) | ||
|
||
// Authenticator 包裝了 google OTP 和 token 的管理 | ||
type Authenticator interface { | ||
Token(pin string) (string, error) | ||
Valid(token string) bool | ||
URI(user string) string // 取得認證用 uri | ||
} | ||
|
||
// ErrPincode 代表 pin 碼檢查失敗 | ||
type ErrPincode struct { | ||
Pin string | ||
} | ||
|
||
func (e ErrPincode) Error() string { | ||
return "pin code " + e.Pin + " incorrect" | ||
} | ||
|
||
type authenticator struct { | ||
current string // current token | ||
otp *dgoogauth.OTPConfig | ||
lock *sync.Mutex | ||
} | ||
|
||
func (a *authenticator) Token(pin string) (string, error) { | ||
// 避免 data racing 所以上個鎖 | ||
a.lock.Lock() | ||
defer a.lock.Unlock() | ||
|
||
if ok, err := a.otp.Authenticate(pin); err != nil || !ok { | ||
return "", ErrPincode{pin} | ||
} | ||
|
||
// 產生隨機的 token | ||
// 最完美的方法:用亂數然後轉字串(不對 | ||
token := a.current | ||
for token == a.current { | ||
token = strconv.Itoa(rand.Int()) | ||
} | ||
|
||
a.current = token | ||
|
||
return token, nil | ||
} | ||
|
||
func (a *authenticator) Valid(token string) bool { | ||
return token == a.current | ||
} | ||
|
||
func (a *authenticator) URI(user string) string { | ||
return a.otp.ProvisionURIWithIssuer(user, "xchg") | ||
} | ||
|
||
// NewAuthenticator 建立一個 10 秒間隔的 totp Authenticator | ||
// | ||
// secret 是 80bit 經過 base32 編碼後的字串 | ||
func NewAuthenticator(secret string) Authenticator { | ||
return &authenticator{ | ||
"", | ||
&dgoogauth.OTPConfig{ | ||
Secret: secret, | ||
WindowSize: 10, | ||
}, | ||
&sync.Mutex{}, | ||
} | ||
} |
Oops, something went wrong.