WIP login flow
This commit is contained in:
parent
557f9085f6
commit
f13dc593b0
|
@ -16,7 +16,6 @@ import (
|
|||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.coolaj86.com/coolaj86/go-mockid/xkeypairs"
|
||||
"git.rootprojects.org/root/keypairs"
|
||||
//jwt "github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
@ -104,7 +103,7 @@ func GenToken(host string, privkey keypairs.PrivateKey, query url.Values) (strin
|
|||
protected := fmt.Sprintf(`{"typ":"JWT","alg":%q,"kid":"%s"}`, alg, thumbprint)
|
||||
protected64 := base64.RawURLEncoding.EncodeToString([]byte(protected))
|
||||
|
||||
exp, err := xkeypairs.ParseDuration(query.Get("exp"))
|
||||
exp, err := time.ParseDuration(query.Get("exp"))
|
||||
if nil != err {
|
||||
// cryptic error code
|
||||
// TODO propagate error
|
||||
|
@ -113,7 +112,7 @@ func GenToken(host string, privkey keypairs.PrivateKey, query url.Values) (strin
|
|||
|
||||
payload := fmt.Sprintf(
|
||||
`{"iss":"%s/","sub":"dummy","exp":%s}`,
|
||||
host, strconv.FormatInt(time.Now().Add(time.Duration(exp)*time.Second).Unix(), 10),
|
||||
host, strconv.FormatInt(time.Now().Add(exp*time.Second).Unix(), 10),
|
||||
)
|
||||
payload64 := base64.RawURLEncoding.EncodeToString([]byte(payload))
|
||||
|
||||
|
@ -133,8 +132,6 @@ func JOSESign(privkey keypairs.PrivateKey, hash []byte) []byte {
|
|||
case *ecdsa.PrivateKey:
|
||||
r, s, _ := ecdsa.Sign(rndsrc, k, hash[:])
|
||||
rb := r.Bytes()
|
||||
fmt.Println("debug:")
|
||||
fmt.Println(r, s)
|
||||
for len(rb) < 32 {
|
||||
rb = append([]byte{0}, rb...)
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ import (
|
|||
"os"
|
||||
"testing"
|
||||
|
||||
"git.coolaj86.com/coolaj86/go-mockid/mockid/api"
|
||||
"git.coolaj86.com/coolaj86/go-mockid/xkeypairs"
|
||||
"git.rootprojects.org/root/keypairs"
|
||||
//keypairs "github.com/big-squid/go-keypairs"
|
||||
|
@ -35,7 +34,7 @@ func (TestReader) Read(p []byte) (n int, err error) {
|
|||
var testrnd = TestReader{}
|
||||
|
||||
func init() {
|
||||
api.RandomReader = testrnd
|
||||
xkeypairs.RandomReader = testrnd
|
||||
rndsrc = testrnd
|
||||
}
|
||||
|
||||
|
|
143
mockid/route.go
143
mockid/route.go
|
@ -43,28 +43,57 @@ type OTPResponse struct {
|
|||
HTTPResponse
|
||||
}
|
||||
|
||||
type Contact struct {
|
||||
Email string `json:"email"`
|
||||
Subjects []string `json:"subjects"`
|
||||
}
|
||||
|
||||
type Subject struct {
|
||||
Subject string `json:"subject"`
|
||||
Emails map[string]time.Time `json:"emails"`
|
||||
}
|
||||
|
||||
var privKeyJWKPath string
|
||||
var tokenPrefix string
|
||||
var contactPrefix string
|
||||
|
||||
// Route returns an HTTP Mux containing the full API
|
||||
func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler {
|
||||
Init()
|
||||
|
||||
contactPrefix := jwksPrefix + "/contacts"
|
||||
contactKV := kvdb.KVDB{
|
||||
Prefix: jwksPrefix + "/contacts",
|
||||
Prefix: contactPrefix,
|
||||
Ext: "eml.json",
|
||||
}
|
||||
|
||||
subjectPrefix := jwksPrefix + "/subjects"
|
||||
subjectKV := kvdb.KVDB{
|
||||
Prefix: subjectPrefix,
|
||||
Ext: "sub.json",
|
||||
}
|
||||
|
||||
// TODO get from main()
|
||||
tokenPrefix = jwksPrefix + "/tokens"
|
||||
contactPrefix = jwksPrefix + "/contacts"
|
||||
for _, pre := range []string{tokenPrefix, contactPrefix} {
|
||||
for _, pre := range []string{tokenPrefix, contactPrefix, subjectPrefix} {
|
||||
if err := os.MkdirAll(pre, 0750); nil != err {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
pubkey := keypairs.NewPublicKey(privkey.Public())
|
||||
|
||||
privKeyJWKPath = jwksPrefix + "/private/priv.jwk.json"
|
||||
signingKey, err := xkeypairs.ParsePrivateKeyFile(privKeyJWKPath)
|
||||
if nil != err {
|
||||
signingKey = xkeypairs.GenPrivKey(&xkeypairs.KeyOptions{})
|
||||
_ = os.MkdirAll(jwksPrefix+"/private", 0750)
|
||||
b := xkeypairs.MarshalJWKPrivateKey(signingKey)
|
||||
if err := ioutil.WriteFile(privKeyJWKPath, b, 0600); nil != err {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
signingPub := keypairs.NewPublicKey(signingKey.Public())
|
||||
//signingKid := keypairs.ThumbprintPublicKey(signingPub)
|
||||
|
||||
http.HandleFunc("/api/new-hashcash", func(w http.ResponseWriter, r *http.Request) {
|
||||
if "POST" != r.Method {
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
|
@ -97,7 +126,7 @@ func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler {
|
|||
query := r.URL.Query()
|
||||
contact := strings.Replace(strings.TrimPrefix(query.Get("contact"), "mailto:"), " ", "+", -1)
|
||||
if "" == contact {
|
||||
fmt.Println("got here 3a")
|
||||
fmt.Println("meta: empty contact query")
|
||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||
b, _ := json.Marshal(&HTTPResponse{
|
||||
Error: "missing require query parameter 'contact'",
|
||||
|
@ -108,8 +137,7 @@ func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler {
|
|||
|
||||
_, ok, err := contactKV.Load(contact)
|
||||
if nil != err {
|
||||
fmt.Println("got here 3b")
|
||||
fmt.Fprintf(os.Stderr, "bad things:", err.Error())
|
||||
fmt.Fprintf(os.Stderr, "meta: error loading contact: %s\n", err.Error())
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
@ -128,6 +156,7 @@ func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler {
|
|||
w.Write(b)
|
||||
})
|
||||
|
||||
// TODO rename as challenge?
|
||||
http.HandleFunc("/api/authn/verify", func(w http.ResponseWriter, r *http.Request) {
|
||||
baseURL := getBaseURL(r)
|
||||
query := r.URL.Query()
|
||||
|
@ -155,6 +184,7 @@ func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler {
|
|||
w.Write(b)
|
||||
})
|
||||
|
||||
// TODO rename as present?
|
||||
http.HandleFunc("/api/authn/consume", func(w http.ResponseWriter, r *http.Request) {
|
||||
query := r.URL.Query()
|
||||
otpSecret := query.Get("secret")
|
||||
|
@ -193,6 +223,78 @@ func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler {
|
|||
return
|
||||
}
|
||||
|
||||
var contact *Contact
|
||||
val, ok, err := contactKV.Load(otp.Email, &Contact{})
|
||||
if nil != err {
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
fmt.Println("error loading contact:", err)
|
||||
fmt.Fprintf(w, "%s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if ok {
|
||||
contact = val.(*Contact)
|
||||
} else {
|
||||
// TODO c := Contacts.New(); c.Save();
|
||||
uuid, _ := uuid.NewRandom()
|
||||
uuidBin, _ := uuid.MarshalBinary()
|
||||
sub := base64.RawURLEncoding.EncodeToString(uuidBin)
|
||||
contact = &Contact{
|
||||
Email: otp.Email,
|
||||
Subjects: []string{sub},
|
||||
}
|
||||
if err := contactKV.Store(otp.Email, contact); nil != err {
|
||||
fmt.Println("ERROR contactKV Store", err)
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "%s", err)
|
||||
return
|
||||
}
|
||||
subject := &Subject{
|
||||
Subject: sub,
|
||||
Emails: map[string]time.Time{
|
||||
otp.Email: time.Now(),
|
||||
},
|
||||
}
|
||||
if err := subjectKV.Store(sub, subject); nil != err {
|
||||
fmt.Println("ERROR subjectKV Store", err)
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "%s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var sub string
|
||||
if len(contact.Subjects) < 1 || len(contact.Subjects) > 1 {
|
||||
fmt.Printf("%#v\n", contact)
|
||||
if len(contact.Subjects) > 1 {
|
||||
fmt.Fprintf(os.Stderr, "TODO: give exchange token for multiple accounts\n")
|
||||
}
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "%s", err)
|
||||
return
|
||||
}
|
||||
sub = contact.Subjects[0]
|
||||
|
||||
uuid, _ := uuid.NewRandom()
|
||||
nonce, _ := uuid.MarshalBinary()
|
||||
baseURL := getBaseURL(r)
|
||||
tok, err := xkeypairs.SignClaims(
|
||||
signingKey,
|
||||
xkeypairs.Object{},
|
||||
xkeypairs.Object{
|
||||
"sub": sub,
|
||||
"iss": baseURL,
|
||||
"jti": base64.RawURLEncoding.EncodeToString(nonce),
|
||||
"iat": time.Now().Unix(),
|
||||
"exp": "1h",
|
||||
},
|
||||
)
|
||||
if nil != err {
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "%s", err)
|
||||
return
|
||||
}
|
||||
otp.AccessToken = xkeypairs.JWSToJWT(tok)
|
||||
b, _ := json.Marshal(&OTPResponse{
|
||||
HTTPResponse: HTTPResponse{Success: true},
|
||||
OTP: *otp,
|
||||
|
@ -488,7 +590,7 @@ func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler {
|
|||
http.Error(w, "Bad Format: malformed token, or malformed jwk at issuer url", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, string(tokenB))
|
||||
fmt.Fprintf(w, string(tokenB)+"\n")
|
||||
})
|
||||
|
||||
http.HandleFunc("/authorization_header", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -540,14 +642,20 @@ func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler {
|
|||
if nil != err {
|
||||
//http.Error(w, "Not Found", http.StatusNotFound)
|
||||
exp := strconv.FormatInt(time.Now().Add(15*time.Minute).Unix(), 10)
|
||||
jwk := string(keypairs.MarshalJWKPublicKey(pubkey))
|
||||
jwk = strings.Replace(jwk, `{"`, `{ "`, 1)
|
||||
jwk = strings.Replace(jwk, `",`, `" ,`, -1)
|
||||
jwk = jwk[0 : len(jwk)-1]
|
||||
jwk = jwk + fmt.Sprintf(`, "ext": true , "key_ops": ["verify"], "exp": %s }`, exp)
|
||||
jwkReal := string(keypairs.MarshalJWKPublicKey(signingPub))
|
||||
jwkReal = strings.Replace(jwkReal, `{"`, `{ "`, 1)
|
||||
jwkReal = strings.Replace(jwkReal, `",`, `" ,`, -1)
|
||||
jwkReal = jwkReal[0 : len(jwkReal)-1]
|
||||
jwkReal = jwkReal + fmt.Sprintf(`, "ext": true , "key_ops": ["verify"], "exp": %s, "REAL": true }`, exp)
|
||||
|
||||
jwkFake := string(keypairs.MarshalJWKPublicKey(pubkey))
|
||||
jwkFake = strings.Replace(jwkFake, `{"`, `{ "`, 1)
|
||||
jwkFake = strings.Replace(jwkFake, `",`, `" ,`, -1)
|
||||
jwkFake = jwkFake[0 : len(jwkFake)-1]
|
||||
jwkFake = jwkFake + fmt.Sprintf(`, "ext": true , "key_ops": ["verify"], "exp": %s, "FAKE": true }`, exp)
|
||||
// { "kty": "EC" , "crv": %q , "x": %q , "y": %q , "kid": %q , "ext": true , "key_ops": ["verify"] , "exp": %s }
|
||||
jwkstr := fmt.Sprintf(`{ "keys": [ %s ] }`, jwk)
|
||||
fmt.Println(jwkstr)
|
||||
jwkstr := fmt.Sprintf(`{ "keys": [ %s, %s ] }`, jwkFake, jwkReal)
|
||||
fmt.Println("jwkstr:", jwkstr)
|
||||
fmt.Fprintf(w, jwkstr)
|
||||
return
|
||||
}
|
||||
|
@ -565,7 +673,7 @@ func Route(jwksPrefix string, privkey keypairs.PrivateKey) http.Handler {
|
|||
` "ext": true, "key_ops": ["verify"], "exp": %s } ] }`,
|
||||
tok.Crv, tok.X, tok.Y, tok.KeyID, strconv.FormatInt(time.Now().Add(15*time.Minute).Unix(), 10),
|
||||
)
|
||||
fmt.Println(jwkstr)
|
||||
fmt.Println("jwkstr:", jwkstr)
|
||||
fmt.Fprintf(w, jwkstr)
|
||||
})
|
||||
|
||||
|
@ -582,6 +690,7 @@ type OTP struct {
|
|||
SecretUA string `json:"secret_agent"`
|
||||
SecretIP string `json:"secret_addr"`
|
||||
SecretUsed time.Time `json:"secret_used"`
|
||||
AccessToken string `json:"access_token,omitempty"`
|
||||
}
|
||||
|
||||
func hashOTPSecret(secret []byte) (hash string, receipt string) {
|
||||
|
@ -691,7 +800,7 @@ func checkOTP(hash, email, agent, addr string, consume otpConsumer) (*OTP, error
|
|||
}
|
||||
}
|
||||
|
||||
fmt.Println("SNTHSNTHSNTHSNTHSNTHSNTHSNTHSNTHNSTHSNTH GOOD!!")
|
||||
fmt.Println("THE TOKEN IS GOOD. GOOD!!")
|
||||
|
||||
return &otp, nil
|
||||
}
|
||||
|
|
|
@ -140,14 +140,7 @@ $('.authn-email form').addEventListener('submit', function (ev) {
|
|||
});
|
||||
});
|
||||
|
||||
$('.authn-new-user form').addEventListener('submit', function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
// We don't need to worry about checking if the key exists
|
||||
// even if it does, the account has been deactivated
|
||||
|
||||
setFlow('.authn-container', '.authn-loading');
|
||||
function verifyNewDevice() {
|
||||
return PocketId.auth
|
||||
.verify({ scheme: 'mailto:', email: state.email })
|
||||
.catch(function (err) {
|
||||
|
@ -170,9 +163,31 @@ $('.authn-new-user form').addEventListener('submit', function (ev) {
|
|||
receipt: resp.body.receipt
|
||||
})
|
||||
.then(function (resp) {
|
||||
// this should have a token we can inspect
|
||||
// and return to the calling application.
|
||||
console.log(resp);
|
||||
window.alert('all set!');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
$('.authn-existing form').addEventListener('submit', function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
setFlow('.authn-container', '.authn-loading');
|
||||
verifyNewDevice();
|
||||
});
|
||||
|
||||
$('.authn-new-user form').addEventListener('submit', function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
|
||||
// We don't need to worry about checking if the key exists
|
||||
// even if it does, the account has been deactivated
|
||||
|
||||
setFlow('.authn-container', '.authn-loading');
|
||||
verifyNewDevice();
|
||||
});
|
||||
|
||||
var route = window.location.hash.split('/').slice(1);
|
||||
|
|
Loading…
Reference in New Issue