-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
590 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package reset | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/kvizdos/locksmith/authentication" | ||
"github.com/kvizdos/locksmith/authentication/magic" | ||
"github.com/kvizdos/locksmith/database" | ||
"github.com/kvizdos/locksmith/logger" | ||
"github.com/kvizdos/locksmith/users" | ||
) | ||
|
||
type ResetPasswordAPIHandler struct{} | ||
|
||
type resetPasswordRequest struct { | ||
Password string `json:"password"` | ||
} | ||
|
||
func (r resetPasswordRequest) HasRequiredFields() bool { | ||
return r.Password != "" | ||
} | ||
|
||
func (h ResetPasswordAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||
authUser := r.Context().Value("authUser").(users.LocksmithUser) | ||
db := r.Context().Value("database").(database.DatabaseAccessor) | ||
|
||
body, err := io.ReadAll(r.Body) | ||
if err != nil { | ||
// handle the error | ||
fmt.Println("Error reading request body:", err) | ||
w.WriteHeader(http.StatusBadRequest) | ||
return | ||
} | ||
|
||
var resetReq resetPasswordRequest | ||
err = json.Unmarshal(body, &resetReq) | ||
|
||
if err != nil || (err == nil && !resetReq.HasRequiredFields()) { | ||
logger.LOGGER.Log(logger.BAD_REQUEST, logger.GetIPFromRequest(*r), r.URL.Path) | ||
w.WriteHeader(http.StatusBadRequest) | ||
return | ||
} | ||
|
||
password, err := authentication.CompileLocksmithPassword(resetReq.Password) | ||
|
||
if err != nil { | ||
fmt.Println("Error compiling password:", err) | ||
w.WriteHeader(http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
_, err = db.UpdateOne("users", map[string]interface{}{ | ||
"id": authUser.ID, | ||
}, map[database.DatabaseUpdateActions]map[string]interface{}{ | ||
database.SET: { | ||
"password": password.ToMap(), | ||
"sessions": []interface{}{}, | ||
}, | ||
}) | ||
|
||
cookie, err := r.Cookie("magic") | ||
|
||
if err == nil { | ||
magic.ExpireOld(db, authUser.ID, cookie.Value) | ||
c := &http.Cookie{ | ||
Name: "magic", | ||
Value: "", | ||
Path: "/", | ||
Expires: time.Unix(0, 0), | ||
|
||
HttpOnly: true, | ||
} | ||
http.SetCookie(w, c) | ||
} | ||
|
||
if err != nil { | ||
fmt.Println("Password Reset Error:", err) | ||
w.WriteHeader(http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
w.WriteHeader(http.StatusOK) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package reset | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/kvizdos/locksmith/authentication/endpoints" | ||
"github.com/kvizdos/locksmith/authentication/magic" | ||
"github.com/kvizdos/locksmith/database" | ||
"github.com/kvizdos/locksmith/users" | ||
) | ||
|
||
type ResetRouterAPIHandler struct { | ||
Database database.DatabaseAccessor | ||
SendResetToken func(token string, user users.LocksmithUserInterface) | ||
} | ||
|
||
/* | ||
Flow: | ||
- [ ] User clicks "Forgot password" on login page | ||
- [x] User enters their email | ||
- [x] Show a screen to the user that "if the account exists, we've sent a link to your email address." | ||
- [x] Sends POST request to create & dispatch the reset token | ||
- POST /api/reset-password?email=email | ||
- [x] Create a MAC for the user to access the PUT /api/reset-password endpoint | ||
- [x] Send them a Notification with the MAC (we should make the SendMessage a variable on ResetRouterAPIHandler) | ||
- URL Format: /reset-password/reset?magic=<MAC> | ||
- [x] User will enter their new password | ||
- [x] Password will be changed | ||
- [x] MAC is expired | ||
- PUT /api/reset-password { email: email } | ||
- [x] Redirect to /login | ||
*/ | ||
func (h ResetRouterAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||
switch r.Method { | ||
case http.MethodPatch: | ||
// This page is only accessible through the | ||
// Magic Access Code. | ||
// Updates the actual password | ||
endpoints.SecureEndpointHTTPMiddleware(ResetPasswordAPIHandler{}, h.Database, endpoints.EndpointSecurityOptions{ | ||
MinimalPermissions: []string{"magic.reset.password"}, | ||
PrioritizeMagic: true, | ||
}).ServeHTTP(w, r) | ||
return | ||
case http.MethodPost: | ||
// Creates the MAC if the user exists. | ||
username := r.URL.Query().Get("username") | ||
|
||
if username == "" { | ||
w.WriteHeader(http.StatusBadRequest) | ||
return | ||
} | ||
|
||
user, found := h.Database.FindOne("users", map[string]interface{}{ | ||
"username": username, | ||
}) | ||
|
||
if !found { | ||
w.WriteHeader(http.StatusOK) | ||
return | ||
} | ||
|
||
var lsUser users.LocksmithUserInterface | ||
users.LocksmithUser{}.ReadFromMap(&lsUser, user.(map[string]interface{})) | ||
token, err := lsUser.CreateMagicAuthenticationCode(h.Database, magic.MagicAuthenticationVariables{ | ||
ForUserID: lsUser.GetID(), | ||
AllowedPermissions: []string{"magic.reset.password"}, | ||
TTL: 30 * time.Minute, | ||
}) | ||
|
||
if err != nil { | ||
fmt.Println(err) | ||
w.WriteHeader(http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
h.SendResetToken(token, lsUser) | ||
|
||
w.WriteHeader(http.StatusOK) | ||
default: | ||
w.WriteHeader(http.StatusMethodNotAllowed) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package reset | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
"text/template" | ||
|
||
"github.com/kvizdos/locksmith/pages" | ||
) | ||
|
||
type ResetPasswordPageHandler struct { | ||
AppName string | ||
Styling pages.LocksmithPageStyling | ||
EmailAsUsername bool | ||
ShowResetStage bool | ||
} | ||
|
||
func (h ResetPasswordPageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||
w.Header().Set("Content-Type", "text/html; charset=utf-8") | ||
|
||
tmpl, err := template.New("reset-password.html").Parse(string(pages.ResetPasswordPageHTML)) | ||
|
||
if err != nil { | ||
fmt.Println(err) | ||
} | ||
|
||
type TemplateData struct { | ||
Title string | ||
Styling pages.LocksmithPageStyling | ||
EmailAsUsername bool | ||
ShowResetStage bool | ||
} | ||
inv := TemplateData{ | ||
Title: h.AppName, | ||
Styling: h.Styling, | ||
EmailAsUsername: h.EmailAsUsername, | ||
ShowResetStage: h.ShowResetStage, | ||
} | ||
|
||
if inv.Styling.SubmitColor == "" { | ||
inv.Styling.SubmitColor = "#476ade" | ||
} | ||
|
||
if inv.Styling.StartGradient == "" { | ||
inv.Styling.StartGradient = "#476ade" | ||
} | ||
|
||
if inv.Styling.EndGradient == "" { | ||
inv.Styling.EndGradient = "#2744a3" | ||
} | ||
|
||
if inv.Title == "" { | ||
inv.Title = "Locksmith" | ||
} | ||
|
||
err = tmpl.Execute(w, inv) | ||
|
||
if err != nil { | ||
fmt.Println("Error executing template :", err) | ||
return | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.