diff options
Diffstat (limited to 'api/auth_otp.go')
-rw-r--r-- | api/auth_otp.go | 122 |
1 files changed, 122 insertions, 0 deletions
diff --git a/api/auth_otp.go b/api/auth_otp.go new file mode 100644 index 0000000..de1cf24 --- /dev/null +++ b/api/auth_otp.go | |||
@@ -0,0 +1,122 @@ | |||
1 | package api | ||
2 | |||
3 | import ( | ||
4 | "bytes" | ||
5 | "fmt" | ||
6 | "image/png" | ||
7 | |||
8 | "github.com/gin-gonic/gin" | ||
9 | "github.com/pquerna/otp/totp" | ||
10 | "immae.eu/Immae/Projets/Cryptomonnaies/Cryptoportfolio/Front/db" | ||
11 | ) | ||
12 | |||
13 | func GenerateSecret(user db.User) (bytes.Buffer, string, error) { | ||
14 | var buf bytes.Buffer | ||
15 | key, err := totp.Generate(totp.GenerateOpts{ | ||
16 | Issuer: "cryptoportfolio", | ||
17 | AccountName: user.Email, | ||
18 | Period: 30, | ||
19 | }) | ||
20 | |||
21 | if err != nil { | ||
22 | return buf, "", err | ||
23 | } | ||
24 | |||
25 | qrImage, err := key.Image(200, 200) | ||
26 | if err != nil { | ||
27 | return buf, "", err | ||
28 | } | ||
29 | |||
30 | png.Encode(&buf, qrImage) | ||
31 | if err != nil { | ||
32 | return buf, "", err | ||
33 | } | ||
34 | |||
35 | return buf, key.Secret(), nil | ||
36 | } | ||
37 | |||
38 | type OtpEnrollmentQuery struct { | ||
39 | In struct { | ||
40 | User db.User | ||
41 | } | ||
42 | } | ||
43 | |||
44 | func (q OtpEnrollmentQuery) ValidateParams() *Error { | ||
45 | return nil | ||
46 | } | ||
47 | |||
48 | func (q OtpEnrollmentQuery) Run() (*bytes.Buffer, string, *Error) { | ||
49 | if q.In.User.OtpSecret != "" && q.In.User.IsOtpSetup == true { | ||
50 | return nil, "", &Error{OtpAlreadySetup, "otp is already setup", fmt.Errorf("otp already setup")} | ||
51 | } | ||
52 | |||
53 | buf, secret, err := GenerateSecret(q.In.User) | ||
54 | if err != nil { | ||
55 | return nil, "", NewInternalError(err) | ||
56 | } | ||
57 | |||
58 | err = db.SetOtpSecret(&q.In.User, secret, true) | ||
59 | if err != nil { | ||
60 | return nil, "", NewInternalError(err) | ||
61 | } | ||
62 | |||
63 | return &buf, secret, nil | ||
64 | } | ||
65 | |||
66 | type OtpValidateQuery struct { | ||
67 | In struct { | ||
68 | Pass string | ||
69 | User db.User | ||
70 | Claims JwtClaims | ||
71 | } | ||
72 | |||
73 | Out struct { | ||
74 | Token string `json:"token"` | ||
75 | } | ||
76 | } | ||
77 | |||
78 | func (q OtpValidateQuery) ValidateParams() *Error { | ||
79 | if q.In.Pass == "" { | ||
80 | return &Error{InvalidOtp, "invalid otp", fmt.Errorf("invalid otp '%v'", q.In.Pass)} | ||
81 | } | ||
82 | |||
83 | return nil | ||
84 | } | ||
85 | |||
86 | func (q OtpValidateQuery) Run() (interface{}, *Error) { | ||
87 | var err error | ||
88 | |||
89 | if q.In.User.OtpSecret == "" { | ||
90 | return nil, &Error{OtpNotSetup, "otp is not setup", fmt.Errorf("otp is not setup")} | ||
91 | } | ||
92 | |||
93 | if !totp.Validate(q.In.Pass, q.In.User.OtpSecret) { | ||
94 | return nil, &Error{InvalidOtp, "invalid otp", fmt.Errorf("invalid otp '%v'", q.In.Pass)} | ||
95 | |||
96 | } else if err := db.SetOtpSecret(&q.In.User, q.In.User.OtpSecret, false); err != nil { | ||
97 | return nil, NewInternalError(err) | ||
98 | } | ||
99 | |||
100 | q.In.Claims.Authorized = true | ||
101 | q.Out.Token, err = SignJwt(q.In.Claims) | ||
102 | if err != nil { | ||
103 | return nil, NewInternalError(err) | ||
104 | } | ||
105 | |||
106 | return q.Out, nil | ||
107 | } | ||
108 | |||
109 | func OtpAuth(c *gin.Context) *Error { | ||
110 | claims := GetClaims(c) | ||
111 | user := GetUser(c) | ||
112 | |||
113 | if user.IsOtpSetup == false || user.OtpSecret == "" { | ||
114 | return &Error{OtpNotSetup, "otp is not setup", fmt.Errorf("otp is not setup")} | ||
115 | } | ||
116 | |||
117 | if !claims.Authorized { | ||
118 | return &Error{NeedOtpValidation, "not authorized", fmt.Errorf("otp not authorized")} | ||
119 | } | ||
120 | |||
121 | return nil | ||
122 | } | ||