main.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. package main
  2. import (
  3. "bufio"
  4. "encoding/json"
  5. "fmt"
  6. "io/ioutil"
  7. "net/http"
  8. "os"
  9. "time"
  10. )
  11. type userMapping struct {
  12. SFTPGoUsername string
  13. AuthyID int64
  14. AuthyAPIKey string
  15. }
  16. type keyboardAuthHookResponse struct {
  17. Instruction string `json:"instruction,omitempty"`
  18. Questions []string `json:"questions,omitempty"`
  19. Echos []bool `json:"echos,omitempty"`
  20. AuthResult int `json:"auth_result"`
  21. CheckPwd int `json:"check_password,omitempty"`
  22. }
  23. var (
  24. mapping []userMapping
  25. )
  26. func init() {
  27. // this is for demo only, you probably want to get this mapping dynamically, for example using a database query
  28. mapping = append(mapping, userMapping{
  29. SFTPGoUsername: "<SFTPGo username>",
  30. AuthyID: 1234567,
  31. AuthyAPIKey: "<your api key>",
  32. })
  33. }
  34. func printAuthResponse(result int) {
  35. resp, _ := json.Marshal(keyboardAuthHookResponse{
  36. AuthResult: result,
  37. })
  38. fmt.Printf("%v\n", string(resp))
  39. if result == 1 {
  40. os.Exit(0)
  41. } else {
  42. os.Exit(1)
  43. }
  44. }
  45. func main() {
  46. // get credentials from env vars
  47. username := os.Getenv("SFTPGO_AUTHD_USERNAME")
  48. var userMap userMapping
  49. for _, m := range mapping {
  50. if m.SFTPGoUsername == username {
  51. userMap = m
  52. break
  53. }
  54. }
  55. if userMap.SFTPGoUsername != username {
  56. // no mapping found
  57. os.Exit(1)
  58. }
  59. checkPwdQuestion := keyboardAuthHookResponse{
  60. Instruction: "This is a sample keyboard authentication program that ask for your password + Authy token",
  61. Questions: []string{"Your password: "},
  62. Echos: []bool{false},
  63. CheckPwd: 1,
  64. AuthResult: 0,
  65. }
  66. q, _ := json.Marshal(checkPwdQuestion)
  67. fmt.Printf("%v\n", string(q))
  68. // in a real world app you probably want to use a read timeout
  69. scanner := bufio.NewScanner(os.Stdin)
  70. scanner.Scan()
  71. if scanner.Err() != nil {
  72. printAuthResponse(-1)
  73. }
  74. response := scanner.Text()
  75. if response != "OK" {
  76. printAuthResponse(-1)
  77. }
  78. checkTokenQuestion := keyboardAuthHookResponse{
  79. Instruction: "",
  80. Questions: []string{"Authy token: "},
  81. Echos: []bool{false},
  82. CheckPwd: 0,
  83. AuthResult: 0,
  84. }
  85. q, _ = json.Marshal(checkTokenQuestion)
  86. fmt.Printf("%v\n", string(q))
  87. scanner.Scan()
  88. if scanner.Err() != nil {
  89. printAuthResponse(-1)
  90. }
  91. authyToken := scanner.Text()
  92. url := fmt.Sprintf("https://api.authy.com/protected/json/verify/%v/%v", authyToken, userMap.AuthyID)
  93. req, err := http.NewRequest(http.MethodGet, url, nil)
  94. if err != nil {
  95. printAuthResponse(-1)
  96. }
  97. req.Header.Set("X-Authy-API-Key", userMap.AuthyAPIKey)
  98. httpClient := &http.Client{
  99. Timeout: 10 * time.Second,
  100. }
  101. resp, err := httpClient.Do(req)
  102. if err != nil {
  103. printAuthResponse(-1)
  104. }
  105. defer resp.Body.Close()
  106. if resp.StatusCode != http.StatusOK {
  107. // status code 200 is expected
  108. printAuthResponse(-1)
  109. }
  110. var authyResponse map[string]interface{}
  111. respBody, err := ioutil.ReadAll(resp.Body)
  112. if err != nil {
  113. printAuthResponse(-1)
  114. }
  115. err = json.Unmarshal(respBody, &authyResponse)
  116. if err != nil {
  117. printAuthResponse(-1)
  118. }
  119. if authyResponse["success"].(string) == "true" {
  120. printAuthResponse(1)
  121. }
  122. printAuthResponse(-1)
  123. }