WIP login flow

This commit is contained in:
AJ ONeal 2020-09-16 22:37:19 +00:00
parent 557f9085f6
commit f13dc593b0
4 changed files with 152 additions and 32 deletions

View File

@ -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...)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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);