ref: master
auth/middleware.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
package auth import ( "context" "fmt" "errors" "net/http" "strings" "time" "encoding/json" "log" "golang.org/x/crypto/bcrypt" "git.eletrotupi.com/git/dinheiro/db" "git.eletrotupi.com/git/dinheiro/types" "git.eletrotupi.com/git/dinheiro/keys" ) var authCtxKey = &contextKey{"auth"} type contextKey struct { name string } // This is the representation of our currentUser type AuthContext struct { UserID int Email string CreatedAt time.Time UpdatedAt time.Time } type AuthCookie struct { Email string `json:"email"` } func authError(writer http.ResponseWriter, reason string, statusCode int) { writer.WriteHeader(statusCode) writer.Write([]byte(reason)) } func LookupUserAndGenerateCtx(ctx context.Context, email string) (*AuthContext, error) { var ( authCtx AuthContext err error ) dbConn := db.ForContext(ctx) row := dbConn.QueryRow(`SELECT id, email, created_at, updated_at FROM users WHERE email = $1`, email) if err = row.Scan(&authCtx.UserID, &authCtx.Email, &authCtx.CreatedAt, &authCtx.UpdatedAt); err != nil { return nil, fmt.Errorf("Something went off when creating the auth context %s", err) } return &authCtx, nil } // This method authenticate the user, populate a AuthContext and set the cookie // on the request func authenticate(w http.ResponseWriter, r *http.Request, next http.Handler) { var ( //authCookie AuthCookie user types.User err error ) err = r.ParseForm() if err != nil { authError(w, "Couldn't parse form", http.StatusInternalServerError) } email := r.Form.Get("email") password := r.Form.Get("password") log.Printf("User email %s", email) dbConn := db.ForContext(r.Context()) row := dbConn.QueryRow( `SELECT id, email, encrypted_password FROM users WHERE email = $1`, email) if err = row.Scan(&user.ID, &user.Email, &user.EncryptedPassword); err != nil { authError(w, "Email wasn't found", http.StatusUnauthorized) return } err = bcrypt.CompareHashAndPassword([]byte(user.EncryptedPassword), []byte(password)) if err != nil { authError(w, "Incorrect Password", http.StatusUnauthorized) return } authCtx, err := LookupUserAndGenerateCtx(r.Context(), user.Email) if err != nil { authError(w, err.Error(), http.StatusForbidden) return } authCookie := AuthCookie{Email: user.Email} cookieJson, err := json.Marshal(authCookie) signedCookie := keys.Encrypt(cookieJson) cookie := http.Cookie{Name: "dinheiro.v1", Value: string(signedCookie)} http.SetCookie(w, &cookie) ctx := context.WithValue(r.Context(), authCtxKey, authCtx) r = r.WithContext(ctx) next.ServeHTTP(w, r) } // This authenticate from an existing cookie sent by the browser // It'll decrypt the cookie, unmarshal into a AuthCookie struct // Then populate the AuthContext (our current user) func authFromCookie(cookie *http.Cookie, w http.ResponseWriter, r *http.Request, next http.Handler) { payload := keys.DecryptWithoutExpiration([]byte(cookie.Value)) if payload == nil { authError(w, "Invalid cookie content", http.StatusForbidden) } var authCookie AuthCookie err := json.Unmarshal(payload, &authCookie) if err != nil { panic("aaaaa") } authCtx, err := LookupUserAndGenerateCtx(r.Context(), authCookie.Email) if err != nil { authError(w, err.Error(), http.StatusForbidden) return } ctx := context.WithValue(r.Context(), authCtxKey, authCtx) r = r.WithContext(ctx) next.ServeHTTP(w, r) } func Middleware() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie("dinheiro.v1") if err == nil { authFromCookie(cookie, w, r, next) return } if strings.HasPrefix(r.URL.Path, "/auth") { authenticate(w, r, next) return } next.ServeHTTP(w, r) return }) } } func ForContext(ctx context.Context) (*AuthContext, error) { raw, ok := ctx.Value(authCtxKey).(*AuthContext) if !ok { return nil, errors.New("No User") } return raw, nil } |