github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/model/oauth/confirm_flagship.go (about) 1 package oauth 2 3 import ( 4 "crypto/sha256" 5 "encoding/base32" 6 "io" 7 "time" 8 9 "github.com/cozy/cozy-stack/model/instance" 10 "github.com/cozy/cozy-stack/model/job" 11 "github.com/cozy/cozy-stack/model/settings" 12 "github.com/cozy/cozy-stack/pkg/crypto" 13 "github.com/pquerna/otp" 14 "github.com/pquerna/otp/totp" 15 "golang.org/x/crypto/hkdf" 16 ) 17 18 var macConfig = crypto.MACConfig{ 19 Name: "confirm-flagship", 20 MaxAge: 0, 21 MaxLen: 256, 22 } 23 24 var totpOptions = totp.ValidateOpts{ 25 Period: 30, // 30s 26 Skew: 10, // 30s +- 10*30s = [-5min; 5,5min] 27 Digits: otp.DigitsSix, 28 Algorithm: otp.AlgorithmSHA256, 29 } 30 31 // SendConfirmFlagshipCode sends by mail a code to the owner of the instance. 32 // It returns the generated token which can be used to check the code. 33 func SendConfirmFlagshipCode(inst *instance.Instance, clientID string) ([]byte, error) { 34 token, code, err := GenerateConfirmCode(inst, clientID) 35 if err != nil { 36 return nil, err 37 } 38 39 publicName, _ := settings.PublicName(inst) 40 msg, err := job.NewMessage(map[string]interface{}{ 41 "mode": "noreply", 42 "template_name": "confirm_flagship", 43 "template_values": map[string]interface{}{ 44 "Code": code, 45 "PublicName": publicName, 46 }, 47 }) 48 if err != nil { 49 return nil, err 50 } 51 _, err = job.System().PushJob(inst, &job.JobRequest{ 52 WorkerType: "sendmail", 53 Message: msg, 54 }) 55 if err != nil { 56 return nil, err 57 } 58 return token, nil 59 } 60 61 // CheckFlagshipCode returns true if the code is correct and can be used to set 62 // the flagship flag on the given OAuth client. 63 func CheckFlagshipCode(inst *instance.Instance, clientID string, token []byte, code string) bool { 64 salt, err := crypto.DecodeAuthMessage(macConfig, inst.SessionSecret(), token, nil) 65 if err != nil { 66 return false 67 } 68 69 input := []byte(clientID + "-" + string(inst.SessionSecret())) 70 h := hkdf.New(sha256.New, input, salt, nil) 71 key := make([]byte, 32) 72 _, err = io.ReadFull(h, key) 73 if err != nil { 74 return false 75 } 76 secret := base32.StdEncoding.EncodeToString(key) 77 78 ok, err := totp.ValidateCustom(code, secret, time.Now().UTC(), totpOptions) 79 return ok && err == nil 80 } 81 82 // GenerateConfirmCode generate a 6-digits code and the token to check it. They 83 // can be used to manually confirm that an OAuth client is the flagship app. 84 func GenerateConfirmCode(inst *instance.Instance, clientID string) ([]byte, string, error) { 85 salt := crypto.GenerateRandomBytes(sha256.Size) 86 token, err := crypto.EncodeAuthMessage(macConfig, inst.SessionSecret(), salt, nil) 87 if err != nil { 88 return nil, "", err 89 } 90 91 input := []byte(clientID + "-" + string(inst.SessionSecret())) 92 h := hkdf.New(sha256.New, input, salt, nil) 93 key := make([]byte, 32) 94 _, err = io.ReadFull(h, key) 95 if err != nil { 96 return nil, "", err 97 } 98 secret := base32.StdEncoding.EncodeToString(key) 99 100 code, err := totp.GenerateCodeCustom(secret, time.Now().UTC(), totpOptions) 101 return token, code, err 102 }