Adding response to 2FA
This commit is contained in:
2
cache/cache.go
vendored
2
cache/cache.go
vendored
@ -15,6 +15,7 @@ type UserWithExpired struct {
|
|||||||
Username string
|
Username string
|
||||||
Email string
|
Email string
|
||||||
Password string
|
Password string
|
||||||
|
Totp string
|
||||||
AccessAt time.Time
|
AccessAt time.Time
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
@ -103,6 +104,7 @@ func GetUser(email string) (*UserWithExpired, error) {
|
|||||||
Username: userData.Username,
|
Username: userData.Username,
|
||||||
Email: userData.Email,
|
Email: userData.Email,
|
||||||
Password: userData.Password,
|
Password: userData.Password,
|
||||||
|
Totp: userData.Totp,
|
||||||
AccessAt: time.Now(),
|
AccessAt: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
47
handler/auth/totp/totp.go
Normal file
47
handler/auth/totp/totp.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package totpHandler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/fossyy/filekeeper/session"
|
||||||
|
"github.com/fossyy/filekeeper/types"
|
||||||
|
totpView "github.com/fossyy/filekeeper/view/totp"
|
||||||
|
"github.com/xlzd/gotp"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GET(w http.ResponseWriter, r *http.Request) {
|
||||||
|
component := totpView.Main("Filekeeper - 2FA Page")
|
||||||
|
err := component.Render(r.Context(), w)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func POST(w http.ResponseWriter, r *http.Request) {
|
||||||
|
r.ParseForm()
|
||||||
|
code := r.Form.Get("code")
|
||||||
|
_, user, key := session.GetSession(r)
|
||||||
|
|
||||||
|
totp := gotp.NewDefaultTOTP(user.Totp)
|
||||||
|
if totp.Verify(code, time.Now().Unix()) {
|
||||||
|
storeSession, err := session.Get(key)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(storeSession)
|
||||||
|
storeSession.Values["user"] = types.User{
|
||||||
|
UserID: user.UserID,
|
||||||
|
Email: user.Email,
|
||||||
|
Username: user.Username,
|
||||||
|
Totp: "",
|
||||||
|
Authenticated: true,
|
||||||
|
}
|
||||||
|
http.Redirect(w, r, "/user", http.StatusFound)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
fmt.Fprint(w, "wrong")
|
||||||
|
}
|
||||||
|
}
|
@ -17,6 +17,10 @@ import (
|
|||||||
var log *logger.AggregatedLogger
|
var log *logger.AggregatedLogger
|
||||||
var errorMessages = make(map[string]string)
|
var errorMessages = make(map[string]string)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
errorMessages = map[string]string{
|
errorMessages = map[string]string{
|
||||||
"redirect_uri_mismatch": "The redirect URI provided does not match the one registered with our service. Please contact the administrator for assistance.",
|
"redirect_uri_mismatch": "The redirect URI provided does not match the one registered with our service. Please contact the administrator for assistance.",
|
||||||
@ -92,6 +96,20 @@ func POST(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if email == userData.Email && utils.CheckPasswordHash(password, userData.Password) {
|
if email == userData.Email && utils.CheckPasswordHash(password, userData.Password) {
|
||||||
|
if userData.Totp != "" {
|
||||||
|
storeSession := session.Create()
|
||||||
|
storeSession.Values["user"] = types.User{
|
||||||
|
UserID: userData.UserID,
|
||||||
|
Email: email,
|
||||||
|
Username: userData.Username,
|
||||||
|
Totp: userData.Totp,
|
||||||
|
Authenticated: false,
|
||||||
|
}
|
||||||
|
storeSession.Save(w)
|
||||||
|
http.Redirect(w, r, "/auth/totp", http.StatusSeeOther)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
storeSession := session.Create()
|
storeSession := session.Create()
|
||||||
storeSession.Values["user"] = types.User{
|
storeSession.Values["user"] = types.User{
|
||||||
UserID: userData.UserID,
|
UserID: userData.UserID,
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
googleOauthHandler "github.com/fossyy/filekeeper/handler/auth/google"
|
googleOauthHandler "github.com/fossyy/filekeeper/handler/auth/google"
|
||||||
googleOauthCallbackHandler "github.com/fossyy/filekeeper/handler/auth/google/callback"
|
googleOauthCallbackHandler "github.com/fossyy/filekeeper/handler/auth/google/callback"
|
||||||
googleOauthSetupHandler "github.com/fossyy/filekeeper/handler/auth/google/setup"
|
googleOauthSetupHandler "github.com/fossyy/filekeeper/handler/auth/google/setup"
|
||||||
|
totpHandler "github.com/fossyy/filekeeper/handler/auth/totp"
|
||||||
downloadHandler "github.com/fossyy/filekeeper/handler/download"
|
downloadHandler "github.com/fossyy/filekeeper/handler/download"
|
||||||
downloadFileHandler "github.com/fossyy/filekeeper/handler/download/file"
|
downloadFileHandler "github.com/fossyy/filekeeper/handler/download/file"
|
||||||
forgotPasswordHandler "github.com/fossyy/filekeeper/handler/forgotPassword"
|
forgotPasswordHandler "github.com/fossyy/filekeeper/handler/forgotPassword"
|
||||||
@ -47,7 +48,17 @@ func SetupRoutes() *http.ServeMux {
|
|||||||
default:
|
default:
|
||||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
authRouter.HandleFunc("/totp", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodGet:
|
||||||
|
middleware.Guest(totpHandler.GET, w, r)
|
||||||
|
case http.MethodPost:
|
||||||
|
middleware.Guest(totpHandler.POST, w, r)
|
||||||
|
default:
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
authRouter.HandleFunc("/google/callback", func(w http.ResponseWriter, r *http.Request) {
|
authRouter.HandleFunc("/google/callback", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -176,6 +176,14 @@ func GetSession(r *http.Request) (UserStatus, types.User, string) {
|
|||||||
return Unauthorized, types.User{}, ""
|
return Unauthorized, types.User{}, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !userSession.Authenticated && userSession.Totp != "" {
|
||||||
|
return Unauthorized, userSession, cookie.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if !userSession.Authenticated {
|
||||||
|
return Unauthorized, types.User{}, ""
|
||||||
|
}
|
||||||
|
|
||||||
return Authorized, userSession, cookie.Value
|
return Authorized, userSession, cookie.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ type User struct {
|
|||||||
UserID uuid.UUID
|
UserID uuid.UUID
|
||||||
Email string
|
Email string
|
||||||
Username string
|
Username string
|
||||||
|
Totp string
|
||||||
Authenticated bool
|
Authenticated bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
54
view/totp/totp.templ
Normal file
54
view/totp/totp.templ
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package totpView
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/fossyy/filekeeper/view/layout"
|
||||||
|
)
|
||||||
|
|
||||||
|
templ content(title string) {
|
||||||
|
@layout.Base(title){
|
||||||
|
<main class="container mx-auto px-4 py-12 md:px-6 md:py-16 lg:py-10">
|
||||||
|
<div class="flex min-h-screen items-center justify-center bg-background px-4 py-12 sm:px-6 lg:px-8">
|
||||||
|
<div class="w-full max-w-md space-y-8">
|
||||||
|
<div>
|
||||||
|
<h2 class="mt-6 text-center text-3xl font-bold tracking-tight text-foreground">Verify Your Identity</h2>
|
||||||
|
<p class="mt-2 text-center text-sm text-muted-foreground">
|
||||||
|
Enter the 6-digit code sent to your registered device to complete the login process.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<form class="space-y-6" action="/auth/totp" method="POST">
|
||||||
|
<div>
|
||||||
|
<label for="code" class="block text-sm font-medium text-muted-foreground">
|
||||||
|
Verification Code
|
||||||
|
</label>
|
||||||
|
<div class="mt-1">
|
||||||
|
<input
|
||||||
|
class="h-10 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 block w-full appearance-none rounded-md border border-input bg-background px-3 py-2 placeholder-muted-foreground shadow-sm focus:border-primary focus:outline-none focus:ring-primary sm:text-sm"
|
||||||
|
id="code"
|
||||||
|
autocomplete="one-time-code"
|
||||||
|
required=""
|
||||||
|
placeholder="123456"
|
||||||
|
pattern="[0-9]{6}"
|
||||||
|
maxlength="6"
|
||||||
|
type="text"
|
||||||
|
name="code"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="items-center whitespace-nowrap ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 h-10 flex w-full justify-center rounded-md bg-primary py-2 px-4 text-sm font-medium text-primary-foreground shadow-sm hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Verify Code
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
templ Main(title string) {
|
||||||
|
@content(title)
|
||||||
|
}
|
Reference in New Issue
Block a user