Add initial feed structure, fix passkey id usage
This commit is contained in:
parent
ef91558600
commit
420f6e46c0
6 changed files with 162 additions and 118 deletions
|
@ -3,6 +3,7 @@ package auth
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.mstar.dev/mstar/goutils/other"
|
"git.mstar.dev/mstar/goutils/other"
|
||||||
|
@ -26,38 +27,40 @@ import (
|
||||||
// Returns the credential options the passkey needs to sign
|
// Returns the credential options the passkey needs to sign
|
||||||
func (a *Authenticator) StartPasskeyLogin(
|
func (a *Authenticator) StartPasskeyLogin(
|
||||||
username string,
|
username string,
|
||||||
) (*protocol.CredentialAssertion, uint64, error) {
|
) (*protocol.CredentialAssertion, string, error) {
|
||||||
if ok, err := a.canUsernameLogin(username); !ok {
|
if ok, err := a.canUsernameLogin(username); !ok {
|
||||||
return nil, 0, other.Error("auth", "user may not login", err)
|
return nil, "", other.Error("auth", "user may not login", err)
|
||||||
}
|
}
|
||||||
acc, err := dbgen.User.Where(dbgen.User.Username.Eq(username)).First()
|
acc, err := dbgen.User.Where(dbgen.User.Username.Eq(username)).First()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, other.Error("auth", "failed to acquire user for login", err)
|
return nil, "", other.Error("auth", "failed to acquire user for login", err)
|
||||||
}
|
}
|
||||||
wrappedAcc := fakeUser{acc}
|
wrappedAcc := fakeUser{acc}
|
||||||
options, session, err := a.webauthn.BeginLogin(&wrappedAcc)
|
options, session, err := a.webauthn.BeginLogin(&wrappedAcc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, other.Error("auth", "failed to initiate passkey login", err)
|
return nil, "", other.Error("auth", "failed to initiate passkey login", err)
|
||||||
}
|
}
|
||||||
pkeySession := models.LoginProcessToken{
|
pkeySession := models.LoginProcessToken{
|
||||||
User: *acc,
|
User: *acc,
|
||||||
UserId: acc.ID,
|
UserId: acc.ID,
|
||||||
ExpiresAt: time.Now().Add(time.Minute * 3),
|
ExpiresAt: time.Now().Add(time.Minute * 3),
|
||||||
Token: string(other.Must(json.Marshal(session))),
|
// Abuse name for session storage since token must be a uuid
|
||||||
Name: "",
|
Name: string(other.Must(json.Marshal(session))),
|
||||||
}
|
}
|
||||||
err = dbgen.LoginProcessToken.Clauses(clause.OnConflict{UpdateAll: true}).Create(&pkeySession)
|
err = dbgen.LoginProcessToken.Clauses(clause.OnConflict{UpdateAll: true}).
|
||||||
|
Omit(dbgen.LoginProcessToken.Token).
|
||||||
|
Create(&pkeySession)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, other.Error("auth", "failed to create login process token", err)
|
return nil, "", other.Error("auth", "failed to create login process token", err)
|
||||||
}
|
}
|
||||||
return options, pkeySession.ID, nil
|
return options, pkeySession.Token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complete a passkey login request
|
// Complete a passkey login request
|
||||||
// Takes the username logging in as well as the raw request containing the passkey response
|
// Takes the username logging in as well as the raw request containing the passkey response
|
||||||
func (a *Authenticator) CompletePasskeyLogin(
|
func (a *Authenticator) CompletePasskeyLogin(
|
||||||
username string,
|
username string,
|
||||||
sessionId uint64,
|
sessionId string,
|
||||||
response *http.Request,
|
response *http.Request,
|
||||||
) (accessToken string, err error) {
|
) (accessToken string, err error) {
|
||||||
// Get user in question
|
// Get user in question
|
||||||
|
@ -66,7 +69,7 @@ func (a *Authenticator) CompletePasskeyLogin(
|
||||||
return "", other.Error("auth", "failed to get user for passkey login completion", err)
|
return "", other.Error("auth", "failed to get user for passkey login completion", err)
|
||||||
}
|
}
|
||||||
// Get latest login token data
|
// Get latest login token data
|
||||||
loginToken, err := dbgen.LoginProcessToken.Where(dbgen.LoginProcessToken.ID.Eq(sessionId)).
|
loginToken, err := dbgen.LoginProcessToken.Where(dbgen.LoginProcessToken.Token.Eq(sessionId)).
|
||||||
First()
|
First()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", other.Error(
|
return "", other.Error(
|
||||||
|
@ -80,7 +83,7 @@ func (a *Authenticator) CompletePasskeyLogin(
|
||||||
return "", ErrProcessTimeout
|
return "", ErrProcessTimeout
|
||||||
}
|
}
|
||||||
var pkeySession webauthn.SessionData
|
var pkeySession webauthn.SessionData
|
||||||
err = json.Unmarshal([]byte(loginToken.Token), &pkeySession)
|
err = json.Unmarshal([]byte(loginToken.Name), &pkeySession)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", other.Error("auth", "failed to unmarshal passkey session for user", err)
|
return "", other.Error("auth", "failed to unmarshal passkey session for user", err)
|
||||||
}
|
}
|
||||||
|
@ -89,6 +92,9 @@ func (a *Authenticator) CompletePasskeyLogin(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", other.Error("auth", "passkey completion failed", err)
|
return "", other.Error("auth", "passkey completion failed", err)
|
||||||
}
|
}
|
||||||
|
// TODO: Utilise clone warning
|
||||||
|
// newSession.Authenticator.CloneWarning
|
||||||
|
|
||||||
jsonSessionId, err := json.Marshal(newSession.ID)
|
jsonSessionId, err := json.Marshal(newSession.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", other.Error("auth", "failed to marshal session", err)
|
return "", other.Error("auth", "failed to marshal session", err)
|
||||||
|
@ -129,98 +135,84 @@ func (a *Authenticator) CompletePasskeyLogin(
|
||||||
// Start the process of registrating a passkey to an account
|
// Start the process of registrating a passkey to an account
|
||||||
func (a *Authenticator) StartPasskeyRegistration(
|
func (a *Authenticator) StartPasskeyRegistration(
|
||||||
username string,
|
username string,
|
||||||
) (*protocol.CredentialAssertion, error) {
|
passkeyName string,
|
||||||
// p.l.Infof("begin registration")
|
) (*protocol.CredentialCreation, string, error) {
|
||||||
//
|
if ok, err := a.canUsernameLogin(username); !ok {
|
||||||
// // can we actually do not use the username at all?
|
return nil, "", other.Error("auth", "user may not login", err)
|
||||||
// username, err := getUsername(r)
|
}
|
||||||
//
|
acc, err := dbgen.User.Where(dbgen.User.Username.Eq(username)).First()
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// p.l.Errorf("can't get username: %s", err.Error())
|
return nil, "", other.Error("auth", "failed to acquire user for login", err)
|
||||||
// JSONResponse(w, fmt.Sprintf("can't get username: %s", err.Error()), http.StatusBadRequest)
|
}
|
||||||
//
|
wrappedAcc := fakeUser{acc}
|
||||||
// return
|
options, session, err := a.webauthn.BeginRegistration(&wrappedAcc)
|
||||||
// }
|
jsonSession, err := json.Marshal(session)
|
||||||
//
|
if err != nil {
|
||||||
// user := p.userStore.GetOrCreateUser(username)
|
return nil, "", other.Error("auth", "failed to marshal session to json", err)
|
||||||
//
|
}
|
||||||
// options, session, err := p.webAuthn.BeginRegistration(user)
|
pkeySession := models.LoginProcessToken{
|
||||||
//
|
User: *acc,
|
||||||
// if err != nil {
|
UserId: acc.ID,
|
||||||
// msg := fmt.Sprintf("can't begin registration: %s", err.Error())
|
ExpiresAt: time.Now().Add(time.Minute * 3),
|
||||||
// p.l.Errorf(msg)
|
// Abuse name for storing session and passkey name since token must be a uuid
|
||||||
// JSONResponse(w, msg, http.StatusBadRequest)
|
Name: passkeyName + "---" + string(jsonSession),
|
||||||
//
|
}
|
||||||
// return
|
err = dbgen.LoginProcessToken.Clauses(clause.OnConflict{UpdateAll: true}).
|
||||||
// }
|
Omit(dbgen.LoginProcessToken.Token).
|
||||||
//
|
Create(&pkeySession)
|
||||||
// // Make a session key and store the sessionData values
|
if err != nil {
|
||||||
// t, err := p.sessionStore.GenSessionID()
|
return nil, "", other.Error("auth", "failed to create login process token", err)
|
||||||
//
|
}
|
||||||
// if err != nil {
|
return options, pkeySession.Token, nil
|
||||||
// p.l.Errorf("can't generate session id: %s", err.Error())
|
|
||||||
// JSONResponse(
|
|
||||||
// w,
|
|
||||||
// fmt.Sprintf("can't generate session id: %s", err.Error()),
|
|
||||||
// http.StatusInternalServerError,
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// p.sessionStore.SaveSession(t, session)
|
|
||||||
// p.setSessionCookie(w, t)
|
|
||||||
//
|
|
||||||
// // return the options generated with the session key
|
|
||||||
// // options.publicKey contain our registration options
|
|
||||||
// JSONResponse(w, options, http.StatusOK)
|
|
||||||
|
|
||||||
panic("Not implemented") // TODO: Implement me
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Authenticator) CompletePasskeyRegistration(username string) error {
|
func (a *Authenticator) CompletePasskeyRegistration(
|
||||||
// // Get the session key from cookie
|
username string,
|
||||||
// sid, err := r.Cookie(p.cookieSettings.Name)
|
sessionId string,
|
||||||
//
|
response *http.Request,
|
||||||
// if err != nil {
|
) error {
|
||||||
// p.l.Errorf("can't get session id: %s", err.Error())
|
// Get latest login token data
|
||||||
// JSONResponse(w, fmt.Sprintf("can't get session id: %s", err.Error()), http.StatusBadRequest)
|
loginToken, err := dbgen.LoginProcessToken.Where(dbgen.LoginProcessToken.Token.Eq(sessionId)).
|
||||||
//
|
First()
|
||||||
// return
|
if err != nil {
|
||||||
// }
|
return other.Error(
|
||||||
//
|
"auth",
|
||||||
// // Get the session data stored from the function above
|
"failed to get user's login token for passkey login completion",
|
||||||
// session, ok := p.sessionStore.GetSession(sid.Value)
|
err,
|
||||||
//
|
)
|
||||||
// if !ok {
|
}
|
||||||
// p.l.Errorf("can't get session data")
|
// Check if that token has expired
|
||||||
// JSONResponse(w, "can't get session data", http.StatusBadRequest)
|
if loginToken.ExpiresAt.Before(time.Now()) {
|
||||||
//
|
return ErrProcessTimeout
|
||||||
// return
|
}
|
||||||
// }
|
var pkeySession webauthn.SessionData
|
||||||
//
|
passkeyName, jsonSession, found := strings.Cut(loginToken.Name, "---")
|
||||||
// user := p.userStore.GetUserByWebAuthnId(session.UserID) // Get the user
|
if !found {
|
||||||
//
|
return ErrInvalidPasskeyRegistrationData
|
||||||
// credential, err := p.webAuthn.FinishRegistration(user, *session, r)
|
}
|
||||||
//
|
err = json.Unmarshal([]byte(jsonSession), &pkeySession)
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// msg := fmt.Sprintf("can't finish registration: %s", err.Error())
|
return other.Error("auth", "failed to unmarshal passkey session for user", err)
|
||||||
// p.l.Errorf(msg)
|
}
|
||||||
//
|
wrappedAcc := fakeUser{&loginToken.User}
|
||||||
// p.deleteSessionCookie(w)
|
credential, err := a.webauthn.FinishRegistration(&wrappedAcc, pkeySession, response)
|
||||||
// JSONResponse(w, msg, http.StatusBadRequest)
|
if err != nil {
|
||||||
//
|
return other.Error("auth", "failed to complete passkey registration", err)
|
||||||
// return
|
}
|
||||||
// }
|
jsonCredential, err := json.Marshal(credential)
|
||||||
//
|
if err != nil {
|
||||||
// // If creation was successful, store the credential object
|
return other.Error("auth", "failed to marshal credential to json", err)
|
||||||
// user.PutCredential(*credential)
|
}
|
||||||
// p.userStore.SaveUser(user)
|
authData := models.UserAuthMethod{
|
||||||
//
|
User: loginToken.User,
|
||||||
// p.sessionStore.DeleteSession(sid.Value)
|
UserId: loginToken.UserId,
|
||||||
// p.deleteSessionCookie(w)
|
AuthMethod: models.AuthMethodPasskey,
|
||||||
//
|
Name: passkeyName,
|
||||||
// p.l.Infof("finish registration")
|
Token: jsonCredential,
|
||||||
// JSONResponse(w, "Registration Success", http.StatusOK)
|
}
|
||||||
panic("Not implemented") // TODO: Implement me
|
err = dbgen.UserAuthMethod.Create(&authData)
|
||||||
|
if err != nil {
|
||||||
|
return other.Error("auth", "failed to insert new auth method into db", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
|
// Just a list of all models stored in the database
|
||||||
var AllTypes = []any{
|
var AllTypes = []any{
|
||||||
&Emote{},
|
&Emote{},
|
||||||
|
&Feed{},
|
||||||
&MediaMetadata{},
|
&MediaMetadata{},
|
||||||
&Note{},
|
&Note{},
|
||||||
&NoteToAttachment{},
|
&NoteToAttachment{},
|
||||||
&NoteToEmote{},
|
&NoteToEmote{},
|
||||||
|
&NoteToFeed{},
|
||||||
&NoteToPing{},
|
&NoteToPing{},
|
||||||
&NoteTag{},
|
&NoteTag{},
|
||||||
&Reaction{},
|
&Reaction{},
|
||||||
|
|
32
storage-new/models/Feed.go
Normal file
32
storage-new/models/Feed.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A feed is the initial entry point for all inbound Activitypub events.
|
||||||
|
// However, its primary and only user-facing use case is to be a collection
|
||||||
|
// of inbound messages, nothing else.
|
||||||
|
//
|
||||||
|
// Thus, the flow for inbound events is the following:
|
||||||
|
// If the event is a note:
|
||||||
|
//
|
||||||
|
// Add it to the receiving feed. If it's a reply and the feed is a default
|
||||||
|
// create a notification for the owner
|
||||||
|
//
|
||||||
|
// If it's an event:
|
||||||
|
//
|
||||||
|
// If the feed is not a default feed for a user, discard the event
|
||||||
|
// If it is the default feed for a user, create a notification for the owner
|
||||||
|
type Feed struct {
|
||||||
|
gorm.Model
|
||||||
|
Name string
|
||||||
|
Owner User
|
||||||
|
OwnerId string
|
||||||
|
IsDefault bool // Whether the feed is the default one for the user
|
||||||
|
// If a feed is the default one for a user, use that user's public key.
|
||||||
|
// Otherwise, use its own key
|
||||||
|
PublicKey sql.NullString
|
||||||
|
}
|
|
@ -1,5 +0,0 @@
|
||||||
package models
|
|
||||||
|
|
||||||
// TODO: Struct for mapping a note to a user for their personal feed
|
|
||||||
// Storing timeline info in redis could also be an idea, but I kinda like
|
|
||||||
// everything being in one place
|
|
29
storage-new/models/NoteToFeed.go
Normal file
29
storage-new/models/NoteToFeed.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// TODO: Struct for mapping a note to a user for their personal feed
|
||||||
|
// Storing timeline info in redis could also be an idea, but I kinda like
|
||||||
|
// everything being in one place
|
||||||
|
// Data needed:
|
||||||
|
// - Which note
|
||||||
|
// - Who's feed
|
||||||
|
// - Which feed (once separate feeds are implemented)
|
||||||
|
// - Reason:
|
||||||
|
// - Boost
|
||||||
|
// - Follow person
|
||||||
|
// - Follow tag
|
||||||
|
//
|
||||||
|
// Also need to store the boosts a user has performed somewhere
|
||||||
|
// Maybe adjust Reaction? Though a separate table might be a better option
|
||||||
|
|
||||||
|
// Assigns a note to a feed
|
||||||
|
type NoteToFeed struct {
|
||||||
|
ID uint64 `gorm:"primarykey"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
Note Note
|
||||||
|
NoteId string
|
||||||
|
// Feed Feed
|
||||||
|
// FeedId uint64
|
||||||
|
// Reason AppearanceReason
|
||||||
|
}
|
|
@ -10,15 +10,8 @@ import (
|
||||||
// A user describes an account for creating content.
|
// A user describes an account for creating content.
|
||||||
// This may be controlled by either a human or some external script
|
// This may be controlled by either a human or some external script
|
||||||
//
|
//
|
||||||
// Data stored in external types:
|
// Data stored externally:
|
||||||
// - Custom info fields
|
// - Feed connections (which note belongs in the feed of this user, for what reason)
|
||||||
// - Being types
|
|
||||||
// - Tags
|
|
||||||
// - Relations
|
|
||||||
// - Pronouns
|
|
||||||
// - Roles
|
|
||||||
// - AP remote links
|
|
||||||
// - Auth methods and tokens (hashed pw, totp key, passkey id)
|
|
||||||
type User struct {
|
type User struct {
|
||||||
// ID is a uuid for this account
|
// ID is a uuid for this account
|
||||||
//
|
//
|
||||||
|
|
Loading…
Reference in a new issue