Procházet zdrojové kódy

determine UIN by checking numeric screen names

- Updated the logic to determine if a screen name is a UIN by
checking if the screen name is numeric (contains all integer characters).
- Removed the previous method of inspecting the client user agent
for this determination.
Mike před 1 rokem
rodič
revize
7b4138e61c

+ 1 - 1
README.md

@@ -131,7 +131,7 @@ curl -d'{"screen_name":"MyScreenName", "password":"thepassword"}' http://localho
 ##### ICQ
 
 ```shell
-curl -d'{"screen_name":"100003", "password":"thepassw", "is_icq": true}' http://localhost:8080/user
+curl -d'{"screen_name":"100003", "password":"thepassw"}' http://localhost:8080/user
 ```
 
 #### Delete Users

+ 0 - 3
api.yml

@@ -46,9 +46,6 @@ paths:
                 password:
                   type: string
                   description: The user's password for account creation.
-                is_icq:
-                  type: boolean
-                  description: If true, indicates an ICQ user instead of an AIM user.
       responses:
         '201':
           description: User account created successfully.

+ 19 - 25
foodgroup/auth.go

@@ -7,7 +7,6 @@ import (
 	"fmt"
 	"net"
 	"strconv"
-	"strings"
 
 	"github.com/mk6i/retro-aim-server/config"
 	"github.com/mk6i/retro-aim-server/state"
@@ -78,7 +77,6 @@ func (s AuthService) RegisterChatSession(authCookie []byte) (*state.Session, err
 }
 
 type bosCookie struct {
-	ICQ        uint8                   `oscar:"len_prefix=uint8"`
 	ScreenName state.DisplayScreenName `oscar:"len_prefix=uint8"`
 }
 
@@ -110,7 +108,7 @@ func (s AuthService) RegisterBOSSession(authCookie []byte) (*state.Session, erro
 		sess.SetUserInfoFlag(wire.OServiceUserFlagUnconfirmed)
 	}
 
-	if c.ICQ == 1 {
+	if u.DisplayScreenName.IsUIN() {
 		sess.SetUserInfoFlag(wire.OServiceUserFlagICQ)
 
 		uin, err := strconv.Atoi(u.IdentScreenName.String())
@@ -270,24 +268,23 @@ func (s AuthService) login(
 		return wire.TLVRestBlock{}, errors.New("screen name doesn't exist in tlv")
 	}
 
-	isICQ := false
-	if clientName, hasclientName := TLVList.String(wire.LoginTLVTagsClientIdentity); hasclientName {
-		isICQ = strings.HasPrefix(clientName, "ICQ ")
-	}
+	sn := state.DisplayScreenName(screenName)
 
-	user, err := s.userManager.User(state.NewIdentScreenName(screenName))
+	user, err := s.userManager.User(sn.IdentScreenName())
 	if err != nil {
 		return wire.TLVRestBlock{}, err
 	}
 
 	if user == nil {
 		if s.config.DisableAuth {
-			sn := state.DisplayScreenName(screenName)
-
-			handleValid := (isICQ && sn.ValidateICQHandle() == nil) ||
-				(!isICQ && sn.ValidateAIMHandle() == nil)
+			handleValid := false
+			if sn.IsUIN() {
+				handleValid = sn.ValidateUIN() == nil
+			} else {
+				handleValid = sn.ValidateAIMHandle() == nil
+			}
 			if !handleValid {
-				return loginFailureResponse(screenName, wire.LoginErrInvalidUsernameOrPassword), nil
+				return loginFailureResponse(sn, wire.LoginErrInvalidUsernameOrPassword), nil
 			}
 
 			newUser, err := newUserFn(sn)
@@ -298,18 +295,18 @@ func (s AuthService) login(
 				return wire.TLVRestBlock{}, err
 			}
 
-			return s.loginSuccessResponse(screenName, isICQ, err)
+			return s.loginSuccessResponse(sn, err)
 		}
 
 		loginErr := wire.LoginErrInvalidUsernameOrPassword
-		if isICQ {
+		if sn.IsUIN() {
 			loginErr = wire.LoginErrICQUserErr
 		}
-		return loginFailureResponse(screenName, loginErr), nil
+		return loginFailureResponse(sn, loginErr), nil
 	}
 
 	if s.config.DisableAuth {
-		return s.loginSuccessResponse(screenName, isICQ, err)
+		return s.loginSuccessResponse(sn, err)
 	}
 
 	var loginOK bool
@@ -322,18 +319,15 @@ func (s AuthService) login(
 		loginOK = user.ValidateRoastedPass(roastedPass)
 	}
 	if !loginOK {
-		return loginFailureResponse(screenName, wire.LoginErrInvalidPassword), nil
+		return loginFailureResponse(sn, wire.LoginErrInvalidPassword), nil
 	}
 
-	return s.loginSuccessResponse(screenName, isICQ, err)
+	return s.loginSuccessResponse(sn, err)
 }
 
-func (s AuthService) loginSuccessResponse(screenName string, isICQ bool, err error) (wire.TLVRestBlock, error) {
+func (s AuthService) loginSuccessResponse(screenName state.DisplayScreenName, err error) (wire.TLVRestBlock, error) {
 	loginCookie := bosCookie{
-		ScreenName: state.DisplayScreenName(screenName),
-	}
-	if isICQ {
-		loginCookie.ICQ = 1
+		ScreenName: screenName,
 	}
 
 	buf := &bytes.Buffer{}
@@ -354,7 +348,7 @@ func (s AuthService) loginSuccessResponse(screenName string, isICQ bool, err err
 	}, nil
 }
 
-func loginFailureResponse(screenName string, code uint16) wire.TLVRestBlock {
+func loginFailureResponse(screenName state.DisplayScreenName, code uint16) wire.TLVRestBlock {
 	return wire.TLVRestBlock{
 		TLVList: []wire.TLV{
 			wire.NewTLV(wire.LoginTLVTagsScreenName, screenName),

+ 6 - 10
foodgroup/auth_test.go

@@ -125,7 +125,6 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) {
 							dataIn: func() []byte {
 								loginCookie := bosCookie{
 									ScreenName: user.DisplayScreenName,
-									ICQ:        1,
 								}
 								buf := &bytes.Buffer{}
 								assert.NoError(t, wire.MarshalBE(loginCookie, buf))
@@ -239,9 +238,8 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) {
 			inputSNAC: wire.SNAC_0x17_0x02_BUCPLoginRequest{
 				TLVRestBlock: wire.TLVRestBlock{
 					TLVList: wire.TLVList{
-						wire.NewTLV(wire.LoginTLVTagsClientIdentity, "ICQ 2000b"),
 						wire.NewTLV(wire.LoginTLVTagsPasswordHash, []byte("password")),
-						wire.NewTLV(wire.LoginTLVTagsScreenName, []byte("non_existent_uin")),
+						wire.NewTLV(wire.LoginTLVTagsScreenName, []byte("100003")),
 					},
 				},
 			},
@@ -249,7 +247,7 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) {
 				userManagerParams: userManagerParams{
 					getUserParams: getUserParams{
 						{
-							screenName: state.NewIdentScreenName("non_existent_uin"),
+							screenName: state.NewIdentScreenName("100003"),
 							result:     nil,
 						},
 					},
@@ -263,7 +261,7 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) {
 				Body: wire.SNAC_0x17_0x03_BUCPLoginResponse{
 					TLVRestBlock: wire.TLVRestBlock{
 						TLVList: []wire.TLV{
-							wire.NewTLV(wire.LoginTLVTagsScreenName, state.NewIdentScreenName("non_existent_uin")),
+							wire.NewTLV(wire.LoginTLVTagsScreenName, state.NewIdentScreenName("100003")),
 							wire.NewTLV(wire.LoginTLVTagsErrorSubcode, wire.LoginErrICQUserErr),
 						},
 					},
@@ -556,7 +554,6 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) {
 							dataIn: func() []byte {
 								loginCookie := bosCookie{
 									ScreenName: user.DisplayScreenName,
-									ICQ:        1,
 								}
 								buf := &bytes.Buffer{}
 								assert.NoError(t, wire.MarshalBE(loginCookie, buf))
@@ -648,7 +645,7 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) {
 					TLVList: wire.TLVList{
 						wire.NewTLV(wire.LoginTLVTagsClientIdentity, "ICQ 2000b"),
 						wire.NewTLV(wire.LoginTLVTagsRoastedPassword, roastedPassword),
-						wire.NewTLV(wire.LoginTLVTagsScreenName, []byte("non_existent_uin")),
+						wire.NewTLV(wire.LoginTLVTagsScreenName, []byte("100003")),
 					},
 				},
 			},
@@ -656,7 +653,7 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) {
 				userManagerParams: userManagerParams{
 					getUserParams: getUserParams{
 						{
-							screenName: state.NewIdentScreenName("non_existent_uin"),
+							screenName: state.NewIdentScreenName("100003"),
 							result:     nil,
 						},
 					},
@@ -664,7 +661,7 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) {
 			},
 			expectOutput: wire.TLVRestBlock{
 				TLVList: []wire.TLV{
-					wire.NewTLV(wire.LoginTLVTagsScreenName, state.NewIdentScreenName("non_existent_uin")),
+					wire.NewTLV(wire.LoginTLVTagsScreenName, state.NewIdentScreenName("100003")),
 					wire.NewTLV(wire.LoginTLVTagsErrorSubcode, wire.LoginErrICQUserErr),
 				},
 			},
@@ -1042,7 +1039,6 @@ func TestAuthService_RegisterBOSSession(t *testing.T) {
 
 	uin := state.DisplayScreenName("100003")
 	icqAuthCookie := bosCookie{
-		ICQ:        1,
 		ScreenName: uin,
 	}
 	buf = &bytes.Buffer{}

+ 3 - 3
server/http/mgmt_api.go

@@ -199,8 +199,8 @@ func postUserHandler(w http.ResponseWriter, r *http.Request, userManager UserMan
 
 	sn := state.DisplayScreenName(input.ScreenName)
 
-	if input.IsICQ {
-		if err := sn.ValidateICQHandle(); err != nil {
+	if sn.IsUIN() {
+		if err := sn.ValidateUIN(); err != nil {
 			http.Error(w, fmt.Sprintf("invalid uin: %s", err), http.StatusBadRequest)
 			return
 		}
@@ -215,7 +215,7 @@ func postUserHandler(w http.ResponseWriter, r *http.Request, userManager UserMan
 		AuthKey:           newUUID().String(),
 		DisplayScreenName: sn,
 		IdentScreenName:   sn.IdentScreenName(),
-		IsICQ:             input.IsICQ,
+		IsICQ:             sn.IsUIN(),
 	}
 
 	if err := user.HashPassword(input.Password); err != nil {

+ 24 - 3
server/http/mgmt_api_test.go

@@ -169,7 +169,7 @@ func TestUserHandler_POST(t *testing.T) {
 		statusCode       int
 	}{
 		{
-			name: "with valid user",
+			name: "with valid AIM user",
 			body: `{"screen_name":"userA", "password":"thepassword"}`,
 			UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
 			insertUserParams: []insertUserParams{
@@ -188,6 +188,27 @@ func TestUserHandler_POST(t *testing.T) {
 			want:       `User account created successfully.`,
 			statusCode: http.StatusCreated,
 		},
+		{
+			name: "with valid ICQ user",
+			body: `{"screen_name":"100003", "password":"thepass"}`,
+			UUID: uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
+			insertUserParams: []insertUserParams{
+				{
+					user: func() state.User {
+						user := state.User{
+							AuthKey:           uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b").String(),
+							DisplayScreenName: "100003",
+							IdentScreenName:   state.NewIdentScreenName("100003"),
+							IsICQ:             true,
+						}
+						assert.NoError(t, user.HashPassword("thepass"))
+						return user
+					}(),
+				},
+			},
+			want:       `User account created successfully.`,
+			statusCode: http.StatusCreated,
+		},
 		{
 			name:       "with malformed body",
 			body:       `{"screen_name":"userA", "password":"thepassword"`,
@@ -252,14 +273,14 @@ func TestUserHandler_POST(t *testing.T) {
 		},
 		{
 			name:       "invalid ICQ UIN",
-			body:       `{"screen_name":"1000", "password":"thepass", "is_icq":true}`,
+			body:       `{"screen_name":"1000", "password":"thepass"}`,
 			UUID:       uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
 			want:       `invalid uin: uin must be a number in the range 10000-2147483646`,
 			statusCode: http.StatusBadRequest,
 		},
 		{
 			name:       "invalid ICQ password",
-			body:       `{"screen_name":"100003", "password":"thelongpassword", "is_icq":true}`,
+			body:       `{"screen_name":"100003", "password":"thelongpassword"}`,
 			UUID:       uuid.MustParse("07c70701-ba68-49a9-9f9b-67a53816e37b"),
 			want:       `invalid password: invalid password length: password must be between 6-8 characters`,
 			statusCode: http.StatusBadRequest,

+ 0 - 1
server/http/types.go

@@ -39,7 +39,6 @@ type MessageRelayer interface {
 type userWithPassword struct {
 	ScreenName string `json:"screen_name"`
 	Password   string `json:"password,omitempty"`
-	IsICQ      bool   `json:"is_icq"`
 }
 
 type onlineUsers struct {

+ 15 - 2
state/user.go

@@ -105,11 +105,24 @@ func (s DisplayScreenName) ValidateAIMHandle() error {
 	return nil
 }
 
-// ValidateICQHandle returns an error if the instance is not a valid ICQ UIN.
+// IsUIN indicates whether the screen name is an ICQ UIN.
+func (s DisplayScreenName) IsUIN() bool {
+	if len(s) == 0 {
+		return false
+	}
+	for _, r := range s {
+		if !unicode.IsDigit(r) {
+			return false
+		}
+	}
+	return true
+}
+
+// ValidateUIN returns an error if the instance is not a valid ICQ UIN.
 // Possible errors:
 //   - ErrICQUINInvalidFormat: if the UIN is not a number or is not in the valid
 //     range
-func (s DisplayScreenName) ValidateICQHandle() error {
+func (s DisplayScreenName) ValidateUIN() error {
 	uin, err := strconv.Atoi(string(s))
 	if err != nil || uin < 10000 || uin > 2147483646 {
 		return ErrICQUINInvalidFormat

+ 3 - 3
state/user_test.go

@@ -251,11 +251,11 @@ func TestDisplayScreenName_ValidateICQHandle(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			err := tt.input.ValidateICQHandle()
+			err := tt.input.ValidateUIN()
 			if tt.wantErr != nil {
-				assert.ErrorIs(t, err, tt.wantErr, "ValidateICQHandle() error = %v, wantErr %v", err, tt.wantErr)
+				assert.ErrorIs(t, err, tt.wantErr, "ValidateUIN() error = %v, wantErr %v", err, tt.wantErr)
 			} else {
-				assert.NoError(t, err, "ValidateICQHandle() error = %v, wantErr %v", err, tt.wantErr)
+				assert.NoError(t, err, "ValidateUIN() error = %v, wantErr %v", err, tt.wantErr)
 			}
 		})
 	}