Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Additional admin functions #50

Merged
merged 2 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func main() {
}()
go func(logger *slog.Logger) {
logger = logger.With("svc", "BOS")
authService := foodgroup.NewAuthService(cfg, sessionManager, chatSessionManager, feedbagStore, adjListBuddyListStore, cookieBaker, sessionManager, feedbagStore, chatSessionManager)
authService := foodgroup.NewAuthService(cfg, sessionManager, chatSessionManager, feedbagStore, adjListBuddyListStore, cookieBaker, sessionManager, feedbagStore, chatSessionManager, feedbagStore)
bartService := foodgroup.NewBARTService(logger, feedbagStore, sessionManager, feedbagStore, adjListBuddyListStore)
buddyService := foodgroup.NewBuddyService(sessionManager, feedbagStore, adjListBuddyListStore)
chatNavService := foodgroup.NewChatNavService(logger, feedbagStore, state.NewChatRoom)
Expand Down Expand Up @@ -85,7 +85,7 @@ func main() {
go func(logger *slog.Logger) {
logger = logger.With("svc", "CHAT")
sessionManager := state.NewInMemorySessionManager(logger)
authService := foodgroup.NewAuthService(cfg, sessionManager, chatSessionManager, feedbagStore, adjListBuddyListStore, cookieBaker, sessionManager, feedbagStore, chatSessionManager)
authService := foodgroup.NewAuthService(cfg, sessionManager, chatSessionManager, feedbagStore, adjListBuddyListStore, cookieBaker, sessionManager, feedbagStore, chatSessionManager, feedbagStore)
chatService := foodgroup.NewChatService(chatSessionManager)
oServiceService := foodgroup.NewOServiceServiceForChat(cfg, logger, sessionManager, adjListBuddyListStore, feedbagStore, feedbagStore, chatSessionManager)

Expand All @@ -104,7 +104,7 @@ func main() {
go func(logger *slog.Logger) {
logger = logger.With("svc", "CHAT_NAV")
sessionManager := state.NewInMemorySessionManager(logger)
authService := foodgroup.NewAuthService(cfg, sessionManager, chatSessionManager, feedbagStore, adjListBuddyListStore, cookieBaker, sessionManager, feedbagStore, chatSessionManager)
authService := foodgroup.NewAuthService(cfg, sessionManager, chatSessionManager, feedbagStore, adjListBuddyListStore, cookieBaker, sessionManager, feedbagStore, chatSessionManager, feedbagStore)
chatNavService := foodgroup.NewChatNavService(logger, feedbagStore, state.NewChatRoom)
oServiceService := foodgroup.NewOServiceServiceForChatNav(cfg, logger, sessionManager, adjListBuddyListStore, feedbagStore)

Expand All @@ -124,7 +124,7 @@ func main() {
go func(logger *slog.Logger) {
logger = logger.With("svc", "ALERT")
sessionManager := state.NewInMemorySessionManager(logger)
authService := foodgroup.NewAuthService(cfg, sessionManager, chatSessionManager, feedbagStore, adjListBuddyListStore, cookieBaker, sessionManager, feedbagStore, chatSessionManager)
authService := foodgroup.NewAuthService(cfg, sessionManager, chatSessionManager, feedbagStore, adjListBuddyListStore, cookieBaker, sessionManager, feedbagStore, chatSessionManager, feedbagStore)
oServiceService := foodgroup.NewOServiceServiceForAlert(cfg, logger, sessionManager, adjListBuddyListStore, feedbagStore)

oscar.BOSServer{
Expand All @@ -144,7 +144,7 @@ func main() {
logger = logger.With("svc", "ADMIN")
buddyService := foodgroup.NewBuddyService(sessionManager, feedbagStore, adjListBuddyListStore)
adminService := foodgroup.NewAdminService(sessionManager, feedbagStore, buddyService, sessionManager)
authService := foodgroup.NewAuthService(cfg, sessionManager, chatSessionManager, feedbagStore, adjListBuddyListStore, cookieBaker, sessionManager, feedbagStore, chatSessionManager)
authService := foodgroup.NewAuthService(cfg, sessionManager, chatSessionManager, feedbagStore, adjListBuddyListStore, cookieBaker, sessionManager, feedbagStore, chatSessionManager, feedbagStore)
oServiceService := foodgroup.NewOServiceServiceForAdmin(cfg, logger, buddyService)

oscar.AdminServer{
Expand All @@ -164,7 +164,7 @@ func main() {
logger = logger.With("svc", "BART")
sessionManager := state.NewInMemorySessionManager(logger)
bartService := foodgroup.NewBARTService(logger, feedbagStore, sessionManager, feedbagStore, adjListBuddyListStore)
authService := foodgroup.NewAuthService(cfg, sessionManager, chatSessionManager, feedbagStore, adjListBuddyListStore, cookieBaker, sessionManager, feedbagStore, chatSessionManager)
authService := foodgroup.NewAuthService(cfg, sessionManager, chatSessionManager, feedbagStore, adjListBuddyListStore, cookieBaker, sessionManager, feedbagStore, chatSessionManager, feedbagStore)
oServiceService := foodgroup.NewOServiceServiceForBART(cfg, logger, sessionManager, adjListBuddyListStore, feedbagStore)

oscar.BOSServer{
Expand All @@ -182,7 +182,7 @@ func main() {
}(logger)
go func(logger *slog.Logger) {
logger = logger.With("svc", "AUTH")
authHandler := foodgroup.NewAuthService(cfg, sessionManager, chatSessionManager, feedbagStore, adjListBuddyListStore, cookieBaker, nil, nil, chatSessionManager)
authHandler := foodgroup.NewAuthService(cfg, sessionManager, chatSessionManager, feedbagStore, adjListBuddyListStore, cookieBaker, nil, nil, chatSessionManager, feedbagStore)

oscar.AuthServer{
AuthService: authHandler,
Expand Down
189 changes: 141 additions & 48 deletions foodgroup/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package foodgroup

import (
"context"
"errors"
"net/mail"

"github.com/mk6i/retro-aim-server/state"
"github.com/mk6i/retro-aim-server/wire"
Expand Down Expand Up @@ -32,25 +34,50 @@ type AdminService struct {
messageRelayer MessageRelayer
}

// ConfirmRequest returns the ScreenName account status. It returns SNAC
// wire.AdminConfirmReply. The values in the return SNAC are
// flag, URL, length
func (s AdminService) ConfirmRequest(_ context.Context, frame wire.SNACFrame) (wire.SNACMessage, error) {
return wire.SNACMessage{
Frame: wire.SNACFrame{
FoodGroup: wire.Admin,
SubGroup: wire.AdminAcctConfirmReply,
RequestID: frame.RequestID,
},
Body: wire.SNAC_0x07_0x07_AdminConfirmReply{
Status: wire.AdminAcctConfirmStatusEmailSent, // todo: get from session/db
},
}, nil
// ConfirmRequest will mark the user account as confirmed if the user has an email address set
func (s AdminService) ConfirmRequest(ctx context.Context, sess *state.Session, frame wire.SNACFrame) (wire.SNACMessage, error) {
// getAdminInfoReply returns an AdminAcctConfirmReply SNAC
var getAdminConfirmReply = func(status uint16) wire.SNACMessage {
return wire.SNACMessage{
Frame: wire.SNACFrame{
FoodGroup: wire.Admin,
SubGroup: wire.AdminAcctConfirmReply,
RequestID: frame.RequestID,
},
Body: wire.SNAC_0x07_0x07_AdminConfirmReply{
Status: status,
},
}
}

_, err := s.accountManager.EmailAddressByName(sess.IdentScreenName())
if errors.Is(err, state.ErrNoEmailAddress) {
return getAdminConfirmReply(wire.AdminAcctConfirmStatusServerError), nil
} else if err != nil {
return wire.SNACMessage{}, err
}

accountConfirmed, err := s.accountManager.ConfirmStatusByName(sess.IdentScreenName())
if err != nil {
return wire.SNACMessage{}, err
}
if accountConfirmed {
return getAdminConfirmReply(wire.AdminAcctConfirmStatusAlreadyConfirmed), nil
}
if err := s.accountManager.UpdateConfirmStatus(true, sess.IdentScreenName()); err != nil {
return wire.SNACMessage{}, err
}
sess.ClearUserInfoFlag(wire.OServiceUserFlagUnconfirmed)
if err := s.buddyUpdateBroadcaster.BroadcastBuddyArrived(ctx, sess); err != nil {
return wire.SNACMessage{}, err
}
return getAdminConfirmReply(wire.AdminAcctConfirmStatusEmailSent), nil
}

// InfoQuery returns the requested information about the account
func (s AdminService) InfoQuery(_ context.Context, sess *state.Session, frame wire.SNACFrame, body wire.SNAC_0x07_0x02_AdminInfoQuery) (wire.SNACMessage, error) {
var getAdminInfoReply = func(tag uint16, val any) wire.SNACMessage {
// getAdminInfoReply returns an AdminInfoReply SNAC
var getAdminInfoReply = func(tlvList wire.TLVList) wire.SNACMessage {
return wire.SNACMessage{
Frame: wire.SNACFrame{
FoodGroup: wire.Admin,
Expand All @@ -60,28 +87,38 @@ func (s AdminService) InfoQuery(_ context.Context, sess *state.Session, frame wi
Body: wire.SNAC_0x07_0x03_AdminInfoReply{
Permissions: wire.AdminInfoPermissionsReadWrite, // todo: what does this actually control?
TLVBlock: wire.TLVBlock{
TLVList: wire.TLVList{
wire.NewTLV(tag, val),
},
TLVList: tlvList,
},
},
}
}

// wire.AdminTLVRegistrationStatus is used in the AIM Preferences > Privacy panel to control
// Allow users who know my e-mail address to find...
// o Nothing about me - wire.AdminInfoRegStatusNoDisclosure
// o Only that I have an account - wire.AdminInfoRegStatusLimitDisclosure
// o My screen name - wire.AdminInfoRegStatusFullDisclosure
if _, hasRegStatus := body.TLVRestBlock.Slice(wire.AdminTLVRegistrationStatus); hasRegStatus {
return getAdminInfoReply(wire.AdminTLVRegistrationStatus, wire.AdminInfoRegStatusFullDisclosure), nil // todo: get from session/db
tlvList := wire.TLVList{}

} else if _, hasEmail := body.TLVRestBlock.Slice(wire.AdminTLVEmailAddress); hasEmail {
return getAdminInfoReply(wire.AdminTLVEmailAddress, sess.IdentScreenName().String()+"@aol.com"), nil // todo: get from session/db
if _, hasRegStatus := body.TLVRestBlock.Slice(wire.AdminTLVRegistrationStatus); hasRegStatus {
jgknight marked this conversation as resolved.
Show resolved Hide resolved
regStatus, err := s.accountManager.RegStatusByName(sess.IdentScreenName())
if err != nil {
return wire.SNACMessage{}, err
}
tlvList.Append(wire.NewTLV(wire.AdminTLVRegistrationStatus, regStatus))
return getAdminInfoReply(tlvList), nil
}

} else if _, hasNickName := body.TLVRestBlock.Slice(wire.AdminTLVScreenNameFormatted); hasNickName {
return getAdminInfoReply(wire.AdminTLVScreenNameFormatted, sess.DisplayScreenName().String()), nil
if _, hasEmail := body.TLVRestBlock.Slice(wire.AdminTLVEmailAddress); hasEmail {
e, err := s.accountManager.EmailAddressByName(sess.IdentScreenName())
if errors.Is(err, state.ErrNoEmailAddress) {
tlvList.Append(wire.NewTLV(wire.AdminTLVEmailAddress, ""))
} else if err != nil {
return wire.SNACMessage{}, err
} else {
tlvList.Append(wire.NewTLV(wire.AdminTLVEmailAddress, e.Address))
}
return getAdminInfoReply(tlvList), nil
}

if _, hasNickName := body.TLVRestBlock.Slice(wire.AdminTLVScreenNameFormatted); hasNickName {
tlvList.Append(wire.NewTLV(wire.AdminTLVScreenNameFormatted, sess.DisplayScreenName().String()))
return getAdminInfoReply(tlvList), nil
}

return wire.SNACMessage{
Expand All @@ -96,8 +133,10 @@ func (s AdminService) InfoQuery(_ context.Context, sess *state.Session, frame wi
}, nil
}

// InfoChangeRequest handles the user changing account information
func (s AdminService) InfoChangeRequest(ctx context.Context, sess *state.Session, frame wire.SNACFrame, body wire.SNAC_0x07_0x04_AdminInfoChangeRequest) (wire.SNACMessage, error) {
var replyMessage = func(tag uint16, val any) wire.SNACMessage {
// replyMessage builds and returns an AdminChangeReply SNAC
var getAdminChangeReply = func(tlvList wire.TLVList) wire.SNACMessage {
return wire.SNACMessage{
Frame: wire.SNACFrame{
FoodGroup: wire.Admin,
Expand All @@ -107,39 +146,59 @@ func (s AdminService) InfoChangeRequest(ctx context.Context, sess *state.Session
Body: wire.SNAC_0x07_0x05_AdminChangeReply{
Permissions: wire.AdminInfoPermissionsReadWrite,
TLVBlock: wire.TLVBlock{
TLVList: wire.TLVList{
wire.NewTLV(tag, val),
},
TLVList: tlvList,
},
},
}
}

// validateProposedName ensures that the name is valid
var validateProposedName = func(name state.DisplayScreenName) (ok bool, errorCode uint16) {
// proposed name is too long
if len(name) > 16 {
// proposed name is too long
// todo: 16 should be defined elsewhere
return false, wire.AdminInfoErrorInvalidNickNameLength
} else if name.IdentScreenName() != sess.IdentScreenName() {
// proposed name does not match session name (e.g. malicious client)
}
// proposed name does not match session name (e.g. malicious client)
if name.IdentScreenName() != sess.IdentScreenName() {
return false, wire.AdminInfoErrorValidateNickName
}
// proposed name ends in a space
if name[len(name)-1] == 32 {
return false, wire.AdminInfoErrorInvalidNickName
}
return true, 0
}

// validateProposedEmailAddress ensures that the email address is valid
var validateProposedEmailAddress = func(emailAddress []byte) (e *mail.Address, errorCode uint16) {
/*
todo: pidgin/libpurple will show 'unknown error: 0xNNNN' for these error codes.
We could do a client check here and send wire.AdminInfoErrorDNSFail so pidgin
will show "given email address is invalid" instead.
*/

e, err := mail.ParseAddress(string(emailAddress))

// rfc 5322 basic validation
if err != nil {
return nil, wire.AdminInfoErrorInvalidEmail
}
// rfc 5521 length - local-part (64) + @ (1) + domain (255)
if len(e.Address) > 320 {
return nil, wire.AdminInfoErrorInvalidEmailLength
}
// todo: wire.AdminInfoErrorDNSFail could be sent here for an invalid domain name
return e, 0
}

tlvList := wire.TLVList{}

if sn, hasScreenNameFormatted := body.TLVRestBlock.Slice(wire.AdminTLVScreenNameFormatted); hasScreenNameFormatted {
proposedName := state.DisplayScreenName(sn)
if ok, errorCode := validateProposedName(proposedName); !ok {
return wire.SNACMessage{
Frame: wire.SNACFrame{
FoodGroup: wire.Admin,
SubGroup: wire.AdminErr,
RequestID: frame.RequestID,
},
Body: wire.SNACError{
Code: errorCode,
},
}, nil
tlvList.Append(wire.NewTLV(wire.AdminTLVErrorCode, errorCode))
tlvList.Append(wire.NewTLV(wire.AdminTLVUrl, ""))
return getAdminChangeReply(tlvList), nil
}
if err := s.accountManager.UpdateDisplayScreenName(proposedName); err != nil {
return wire.SNACMessage{}, err
Expand All @@ -157,8 +216,42 @@ func (s AdminService) InfoChangeRequest(ctx context.Context, sess *state.Session
TLVUserInfo: sess.TLVUserInfo(),
},
})
return replyMessage(wire.AdminTLVScreenNameFormatted, proposedName.String()), nil
tlvList.Append(wire.NewTLV(wire.AdminTLVScreenNameFormatted, proposedName.String()))
return getAdminChangeReply(tlvList), nil
}

if emailAddress, hasEmailAddress := body.TLVRestBlock.Slice(wire.AdminTLVEmailAddress); hasEmailAddress {
e, errorCode := validateProposedEmailAddress(emailAddress)
if errorCode != 0 {
tlvList.Append(wire.NewTLV(wire.AdminTLVErrorCode, errorCode))
tlvList.Append(wire.NewTLV(wire.AdminTLVUrl, ""))
return getAdminChangeReply(tlvList), nil

}
if err := s.accountManager.UpdateEmailAddress(e, sess.IdentScreenName()); err != nil {
return wire.SNACMessage{}, err
}
tlvList.Append(wire.NewTLV(wire.AdminTLVEmailAddress, e.Address))
return getAdminChangeReply(tlvList), nil
}

if regStatus, hasRegStatus := body.TLVRestBlock.Uint16(wire.AdminTLVRegistrationStatus); hasRegStatus {
switch regStatus {
case
wire.AdminInfoRegStatusFullDisclosure,
wire.AdminInfoRegStatusLimitDisclosure,
wire.AdminInfoRegStatusNoDisclosure:
if err := s.accountManager.UpdateRegStatus(regStatus, sess.IdentScreenName()); err != nil {
return wire.SNACMessage{}, err
}
tlvList.Append(wire.NewTLV(wire.AdminTLVRegistrationStatus, regStatus))
return getAdminChangeReply(tlvList), nil
}
tlvList.Append(wire.NewTLV(wire.AdminTLVErrorCode, wire.AdminInfoErrorInvalidRegistrationPreference))
tlvList.Append(wire.NewTLV(wire.AdminTLVUrl, ""))
return getAdminChangeReply(tlvList), nil
}

return wire.SNACMessage{
Frame: wire.SNACFrame{
FoodGroup: wire.Admin,
Expand Down
Loading
Loading