change to User.ScreenName, check user verification

This commit is contained in:
Artem Titoulenko 2022-01-16 14:43:48 -05:00
parent 4f345b30da
commit 93bdc4fcbd
13 changed files with 104 additions and 60 deletions

View File

@ -2,12 +2,12 @@ package aimerror
import "github.com/pkg/errors"
func FetchingUser(err error, username string) error {
return errors.Wrapf(err, "could not fetch user with username %s", username)
func FetchingUser(err error, screen_name string) error {
return errors.Wrapf(err, "could not fetch user with screen_name %s", screen_name)
}
func UserNotFound(username string) error {
return errors.Errorf("no user with UIN %s", username)
func UserNotFound(screen_name string) error {
return errors.Errorf("no user with UIN %s", screen_name)
}
var NoUserInSession = errors.New("no user in session")

10
main.go
View File

@ -114,6 +114,8 @@ func main() {
pgdriver.WithWriteTimeout(5*time.Second),
)
log.Printf("DB URL: %s", DB_URL)
// Set up the DB
sqldb := sql.OpenDB(pgconn)
db := bun.NewDB(sqldb, pgdialect.New())
@ -124,7 +126,7 @@ func main() {
db.AddQueryHook(bundebug.NewQueryHook(bundebug.WithVerbose(true)))
// Register our DB models
db.RegisterModel((*models.User)(nil), (*models.Message)(nil), (*models.Buddy)(nil))
db.RegisterModel((*models.User)(nil), (*models.Message)(nil), (*models.Buddy)(nil), (*models.EmailVerification)(nil))
// dev: load in fixtures to test against
fixture := dbfixture.New(db, dbfixture.WithRecreateTables())
@ -169,7 +171,7 @@ func main() {
}
onlineCh <- user
sessionManager.RemoveSession(user.Username)
sessionManager.RemoveSession(user.ScreenName)
}
}
@ -180,10 +182,10 @@ func main() {
}
if user := models.UserFromContext(ctx); user != nil {
fmt.Printf("%s (%v) ->\n%+v\n", user.Username, session.RemoteAddr(), flap)
fmt.Printf("%s (%v) ->\n%+v\n", user.ScreenName, session.RemoteAddr(), flap)
user.LastActivityAt = time.Now()
ctx = models.NewContextWithUser(ctx, user)
sessionManager.SetSession(user.Username, session)
sessionManager.SetSession(user.ScreenName, session)
} else {
fmt.Printf("%v ->\n%+v\n", session.RemoteAddr(), flap)
}

View File

@ -0,0 +1,18 @@
package models
import (
"time"
"github.com/uptrace/bun"
)
// TODO: move this out of here and into API server
type EmailVerification struct {
bun.BaseModel `bun:"table:email_verification"`
UserUIN int64 `bun:",pk,notnull,unique"`
User *User `bun:"rel:has-one,join:user_uin=uin"`
Token string `bun:",notnull"`
Used bool `bun:",notnull,default:false"`
CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"`
UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"`
}

View File

@ -25,12 +25,14 @@ type User struct {
bun.BaseModel `bun:"table:users"`
UIN int64 `bun:",pk,autoincrement"`
Email string `bun:",unique"`
Username string `bun:",unique"`
ScreenName string `bun:",unique"`
Password string
Cipher string
CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"`
UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"`
DeletedAt *time.Time `bun:",nullzero"`
Status UserStatus
Verified bool `bun:",notnull,default:false"`
Profile string
ProfileEncoding string
AwayMessage string
@ -48,9 +50,9 @@ var (
currentUser = userKey("user")
)
func UserByUsername(ctx context.Context, db *bun.DB, username string) (*User, error) {
func UserByScreenName(ctx context.Context, db *bun.DB, screen_name string) (*User, error) {
user := new(User)
if err := db.NewSelect().Model(user).Where("username = ?", username).Scan(ctx, user); err != nil {
if err := db.NewSelect().Model(user).Where("screen_name = ?", screen_name).Scan(ctx, user); err != nil {
if err == sql.ErrNoRows {
return nil, nil
}

View File

@ -1,14 +1,20 @@
- model: User
rows:
- uin: 1
username: toof
- uin: 10000
screen_name: toof
password: bar
email: toof@example.com
- uin: 2
username: artem
verified: true
- uin: 10001
screen_name: artem
password: bar
email: artem@example.com
verified: false
- model: Message
rows: []
- model: Buddy
rows: []
- model: EmailVerification
rows:
- user_uin: 2
token: foobar

View File

@ -25,9 +25,9 @@ func OnlineNotification(sm *SessionManager) (chan *models.User, routineFn) {
}
if user.Status == models.UserStatusOnline {
log.Printf("%s is now online", user.Username)
log.Printf("%s is now online", user.ScreenName)
} else if user.Status == models.UserStatusAway {
log.Printf("%s is now away", user.Username)
log.Printf("%s is now away", user.ScreenName)
}
ctx := context.Background()
@ -44,11 +44,11 @@ func OnlineNotification(sm *SessionManager) (chan *models.User, routineFn) {
if buddy.Source.Status == models.UserStatusAway || buddy.Source.Status == models.UserStatusDnd {
continue
}
log.Printf("telling %s that %s has a new status: %d!", buddy.Source.Username, user.Username, user.Status)
log.Printf("telling %s that %s has a new status: %d!", buddy.Source.ScreenName, user.ScreenName, user.Status)
if s := sm.GetSession(buddy.Source.Username); s != nil {
if s := sm.GetSession(buddy.Source.ScreenName); s != nil {
onlineSnac := oscar.NewSNAC(3, 0xb)
onlineSnac.Data.WriteLPString(user.Username)
onlineSnac.Data.WriteLPString(user.ScreenName)
onlineSnac.Data.WriteUint16(0) // TODO: user warning level
tlvs := []*oscar.TLV{
@ -68,7 +68,7 @@ func OnlineNotification(sm *SessionManager) (chan *models.User, routineFn) {
onlineFlap := oscar.NewFLAP(2)
onlineFlap.Data.WriteBinary(onlineSnac)
if err := s.Send(onlineFlap); err != nil {
log.Printf("could not tell %s that %s is online", buddy.Source.Username, user.Username)
log.Printf("could not tell %s that %s is online", buddy.Source.ScreenName, user.ScreenName)
}
}
}

View File

@ -3,7 +3,7 @@
"version": "1.0.0",
"license": "GPLv4",
"scripts": {
"dev": "nodemon --watch ./ -e go --ignore '*_test.go' --delay 200ms --exec 'go build && ./aim-oscar || exit 1' --signal SIGTERM",
"dev": "nodemon --watch './' -e go,yml --ignore '*_test.go' --delay 200ms --exec 'go build && ./aim-oscar || exit 1' --signal SIGTERM",
"start": "go build && ./aim-oscar"
},
"devDependencies": {

View File

@ -57,7 +57,7 @@ func (g *GenericServiceControls) HandleSNAC(ctx context.Context, db *bun.DB, sna
}
onlineSnac := oscar.NewSNAC(3, 0xb)
onlineSnac.Data.WriteLPString(buddy.Source.Username)
onlineSnac.Data.WriteLPString(buddy.Source.ScreenName)
onlineSnac.Data.WriteUint16(0) // TODO: user warning level
tlvs := []*oscar.TLV{
@ -77,7 +77,7 @@ func (g *GenericServiceControls) HandleSNAC(ctx context.Context, db *bun.DB, sna
onlineFlap := oscar.NewFLAP(2)
onlineFlap.Data.WriteBinary(onlineSnac)
if err := session.Send(onlineFlap); err != nil {
return ctx, errors.Wrapf(err, "could not tell %s that %s is online", buddy.Source.Username, buddy.Target.Username)
return ctx, errors.Wrapf(err, "could not tell %s that %s is online", buddy.Source.ScreenName, buddy.Target.ScreenName)
}
}
@ -132,8 +132,8 @@ func (g *GenericServiceControls) HandleSNAC(ctx context.Context, db *bun.DB, sna
}
onlineSnac := oscar.NewSNAC(1, 0xf)
onlineSnac.Data.WriteUint8(uint8(len(user.Username)))
onlineSnac.Data.WriteString(user.Username)
onlineSnac.Data.WriteUint8(uint8(len(user.ScreenName)))
onlineSnac.Data.WriteString(user.ScreenName)
onlineSnac.Data.WriteUint16(0) // warning level
user.Status = models.UserStatusOnline

View File

@ -86,18 +86,18 @@ func (s *LocationServices) HandleSNAC(ctx context.Context, db *bun.DB, snac *osc
return ctx, errors.Wrap(err, "missing request type")
}
requestedUsername, err := snac.Data.ReadLPString()
requestedScreenName, err := snac.Data.ReadLPString()
if err != nil {
return ctx, errors.Wrap(err, "missing requested username")
return ctx, errors.Wrap(err, "missing requested screen_name")
}
requestedUser, err := models.UserByUsername(ctx, db, requestedUsername)
requestedUser, err := models.UserByScreenName(ctx, db, requestedScreenName)
if err != nil {
return ctx, aimerror.FetchingUser(err, requestedUsername)
return ctx, aimerror.FetchingUser(err, requestedScreenName)
}
respSnac := oscar.NewSNAC(2, 6)
respSnac.Data.WriteLPString(requestedUser.Username)
respSnac.Data.WriteLPString(requestedUser.ScreenName)
respSnac.Data.WriteUint16(0) // TODO: warning level
tlvs := []*oscar.TLV{

View File

@ -43,7 +43,7 @@ func (b *BuddyListManagement) HandleSNAC(ctx context.Context, db *bun.DB, snac *
return ctx, errors.Wrap(err, "expecting more buddies in list")
}
buddy, err := models.UserByUsername(ctx, db, buddyScreename)
buddy, err := models.UserByScreenName(ctx, db, buddyScreename)
if err != nil {
return ctx, errors.Wrap(err, "error looking for User")
}
@ -61,7 +61,7 @@ func (b *BuddyListManagement) HandleSNAC(ctx context.Context, db *bun.DB, snac *
return ctx, err
}
log.Printf("%s added buddy %s to buddy list", user.Username, buddyScreename)
log.Printf("%s added buddy %s to buddy list", user.ScreenName, buddyScreename)
}
return ctx, nil
@ -79,7 +79,7 @@ func (b *BuddyListManagement) HandleSNAC(ctx context.Context, db *bun.DB, snac *
return ctx, errors.Wrap(err, "expecting more buddies in list")
}
buddy, err := models.UserByUsername(ctx, db, buddyScreename)
buddy, err := models.UserByScreenName(ctx, db, buddyScreename)
if err != nil {
return ctx, errors.Wrap(err, "error looking for User")
}
@ -92,7 +92,7 @@ func (b *BuddyListManagement) HandleSNAC(ctx context.Context, db *bun.DB, snac *
return ctx, err
}
log.Printf("%s removed buddy %s from buddy list", user.Username, buddyScreename)
log.Printf("%s removed buddy %s from buddy list", user.ScreenName, buddyScreename)
}
return ctx, nil

View File

@ -181,14 +181,14 @@ func (icbm *ICBM) HandleSNAC(ctx context.Context, db *bun.DB, snac *oscar.SNAC)
// TLV 0x6 is the client telling the server to store the message if the recipient is offline
saveofflineTLV := oscar.FindTLV(tlvs, 6)
if saveofflineTLV != nil {
message, err = models.InsertMessage(ctx, db, msgID, user.Username, to, string(messageContents))
message, err = models.InsertMessage(ctx, db, msgID, user.ScreenName, to, string(messageContents))
if err != nil {
return ctx, errors.Wrap(err, "could not insert message")
}
} else {
message = &models.Message{
Cookie: msgID,
From: user.Username,
From: user.ScreenName,
To: to,
Contents: string(messageContents),
}
@ -204,7 +204,7 @@ func (icbm *ICBM) HandleSNAC(ctx context.Context, db *bun.DB, snac *oscar.SNAC)
ackSnac := oscar.NewSNAC(4, 0xc)
ackSnac.Data.WriteUint64(msgID)
ackSnac.Data.WriteUint16(2)
ackSnac.Data.WriteLPString(user.Username)
ackSnac.Data.WriteLPString(user.ScreenName)
ackFlap := oscar.NewFLAP(2)
ackFlap.Data.WriteBinary(ackSnac)
return ctx, session.Send(ackFlap)

View File

@ -88,19 +88,19 @@ func (a *AuthorizationRegistrationService) HandleSNAC(ctx context.Context, db *b
tlvs, err := oscar.UnmarshalTLVs(snac.Data.Bytes())
util.PanicIfError(err)
usernameTLV := oscar.FindTLV(tlvs, 1)
if usernameTLV == nil {
return ctx, errors.New("missing username TLV")
screenNameTLV := oscar.FindTLV(tlvs, 1)
if screenNameTLV == nil {
return ctx, errors.New("missing screen_name TLV")
}
// Fetch the user
user, err := models.UserByUsername(ctx, db, string(usernameTLV.Data))
user, err := models.UserByScreenName(ctx, db, string(screenNameTLV.Data))
if err != nil {
return ctx, err
}
if user == nil {
snac := oscar.NewSNAC(0x17, 0x03)
snac.Data.WriteBinary(usernameTLV)
snac.Data.WriteBinary(screenNameTLV)
snac.Data.WriteBinary(oscar.NewTLV(0x08, []byte{0, 4}))
resp := oscar.NewFLAP(2)
resp.Data.WriteBinary(snac)
@ -126,21 +126,21 @@ func (a *AuthorizationRegistrationService) HandleSNAC(ctx context.Context, db *b
tlvs, err := oscar.UnmarshalTLVs(snac.Data.Bytes())
util.PanicIfError(err)
usernameTLV := oscar.FindTLV(tlvs, 1)
if usernameTLV == nil {
return ctx, errors.New("missing username TLV 0x1")
screenNameTLV := oscar.FindTLV(tlvs, 1)
if screenNameTLV == nil {
return ctx, errors.New("missing screen_name TLV 0x1")
}
username := string(usernameTLV.Data)
screen_name := string(screenNameTLV.Data)
ctx := context.Background()
user, err := models.UserByUsername(ctx, db, username)
user, err := models.UserByScreenName(ctx, db, screen_name)
if err != nil {
return ctx, err
}
if user == nil {
snac := oscar.NewSNAC(0x17, 0x03)
snac.Data.WriteBinary(usernameTLV)
snac.Data.WriteBinary(screenNameTLV)
snac.Data.WriteBinary(oscar.NewTLV(0x08, []byte{0, 4}))
resp := oscar.NewFLAP(2)
resp.Data.WriteBinary(snac)
@ -162,8 +162,24 @@ func (a *AuthorizationRegistrationService) HandleSNAC(ctx context.Context, db *b
if !bytes.Equal(expectedPasswordHash, passwordHashTLV.Data) {
// Tell the client this was a bad password
badPasswordSnac := oscar.NewSNAC(0x17, 0x03)
badPasswordSnac.Data.WriteBinary(usernameTLV)
badPasswordSnac.Data.WriteBinary(oscar.NewTLV(0x08, []byte{0, 4}))
badPasswordSnac.Data.WriteBinary(screenNameTLV)
badPasswordSnac.Data.WriteBinary(oscar.NewTLV(0x08, []byte{0, 4})) // incorrect nick/pass
badPasswordFlap := oscar.NewFLAP(2)
badPasswordFlap.Data.WriteBinary(badPasswordSnac)
session.Send(badPasswordFlap)
// Tell them to leave
discoFlap := oscar.NewFLAP(4)
return ctx, session.Send(discoFlap)
}
// Only users that have verified their email can use the service
if !user.Verified || user.DeletedAt != nil {
// Tell the client this was a bad password
badPasswordSnac := oscar.NewSNAC(0x17, 0x03)
badPasswordSnac.Data.WriteBinary(screenNameTLV)
badPasswordSnac.Data.WriteBinary(oscar.NewTLV(0x08, []byte{0, 7})) // invalid account
badPasswordSnac.Data.WriteBinary(oscar.NewTLV(0x04, []byte("http://runningman.network/errors/unverified-account")))
badPasswordFlap := oscar.NewFLAP(2)
badPasswordFlap.Data.WriteBinary(badPasswordSnac)
session.Send(badPasswordFlap)
@ -175,7 +191,7 @@ func (a *AuthorizationRegistrationService) HandleSNAC(ctx context.Context, db *b
// Send BOS response + cookie
authSnac := oscar.NewSNAC(0x17, 0x3)
authSnac.Data.WriteBinary(usernameTLV)
authSnac.Data.WriteBinary(screenNameTLV)
authSnac.Data.WriteBinary(oscar.NewTLV(0x5, []byte(a.BOSAddress)))
cookie, err := json.Marshal(AuthorizationCookie{

View File

@ -18,15 +18,15 @@ func NewSessionManager() *SessionManager {
return sm
}
func (sm *SessionManager) SetSession(username string, session *oscar.Session) {
func (sm *SessionManager) SetSession(screen_name string, session *oscar.Session) {
sm.mutex.Lock()
sm.sessions[username] = session
sm.sessions[screen_name] = session
sm.mutex.Unlock()
}
func (sm *SessionManager) GetSession(username string) *oscar.Session {
func (sm *SessionManager) GetSession(screen_name string) *oscar.Session {
sm.mutex.RLock()
s, ok := sm.sessions[username]
s, ok := sm.sessions[screen_name]
sm.mutex.RUnlock()
if ok {
@ -35,8 +35,8 @@ func (sm *SessionManager) GetSession(username string) *oscar.Session {
return nil
}
func (sm *SessionManager) RemoveSession(username string) {
func (sm *SessionManager) RemoveSession(screen_name string) {
sm.mutex.Lock()
sm.sessions[username] = nil
sm.sessions[screen_name] = nil
sm.mutex.Unlock()
}