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