| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- package handlers
- import (
- "context"
- "errors"
- "log/slog"
- "net/http"
- "net/http/httptest"
- "strings"
- "testing"
- "github.com/google/uuid"
- "github.com/stretchr/testify/assert"
- "github.com/mk6i/open-oscar-server/state"
- "github.com/mk6i/open-oscar-server/wire"
- )
- // testAuthService implements AuthService for ClientLogin tests (only FLAPLogin is exercised).
- type testAuthService struct {
- flapLogin func(ctx context.Context, inFrame wire.FLAPSignonFrame, advertisedHost string) (wire.TLVRestBlock, error)
- }
- func (t *testAuthService) BUCPChallenge(ctx context.Context, bodyIn wire.SNAC_0x17_0x06_BUCPChallengeRequest, newUUID func() uuid.UUID) (wire.SNACMessage, error) {
- return wire.SNACMessage{}, nil
- }
- func (t *testAuthService) BUCPLogin(ctx context.Context, bodyIn wire.SNAC_0x17_0x02_BUCPLoginRequest, advertisedHost string) (wire.SNACMessage, error) {
- return wire.SNACMessage{}, nil
- }
- func (t *testAuthService) CrackCookie(authCookie []byte) (state.ServerCookie, error) {
- return state.ServerCookie{}, nil
- }
- func (t *testAuthService) RegisterBOSSession(ctx context.Context, authCookie state.ServerCookie, conf func(sess *state.Session)) (*state.SessionInstance, error) {
- return nil, nil
- }
- func (t *testAuthService) FLAPLogin(ctx context.Context, inFrame wire.FLAPSignonFrame, advertisedHost string) (wire.TLVRestBlock, error) {
- if t.flapLogin != nil {
- return t.flapLogin(ctx, inFrame, advertisedHost)
- }
- return wire.TLVRestBlock{}, nil
- }
- func (t *testAuthService) Signout(ctx context.Context, session *state.Session) {}
- func (t *testAuthService) SignoutChat(ctx context.Context, sess *state.Session) {}
- func successfulLoginBlock() wire.TLVRestBlock {
- var b wire.TLVRestBlock
- b.Append(wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, []byte("fake-auth-cookie-bytes")))
- return b
- }
- func failedLoginBlock() wire.TLVRestBlock {
- var b wire.TLVRestBlock
- b.Append(wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, uint16(1)))
- return b
- }
- type testCookieBaker struct {
- issue func(data []byte) ([]byte, error)
- }
- func (t *testCookieBaker) Issue(data []byte) ([]byte, error) {
- if t.issue != nil {
- return t.issue(data)
- }
- return []byte("issued-cookie"), nil
- }
- func (t *testCookieBaker) Crack(data []byte) ([]byte, error) {
- return data, nil
- }
- func TestAuthHandler_GetToken(t *testing.T) {
- tests := []struct {
- name string
- query string
- cookies []*http.Cookie
- checkBody func(*testing.T, string)
- expectedCode int
- }{
- {
- name: "Success_LocalAuthUserCookie",
- query: "f=json&attributes=loginId&devId=ao1yOLlHVHhsa3o6&c=_callbacks_._0mq8wqdav",
- cookies: []*http.Cookie{
- {Name: "localAuthUser", Value: "testuser||Test User"},
- },
- checkBody: func(t *testing.T, body string) {
- assert.Contains(t, body, "_callbacks_._0mq8wqdav(")
- assert.Contains(t, body, `"statusCode":200`)
- assert.Contains(t, body, `"loginId":"testuser"`)
- assert.Contains(t, body, `"a":`)
- },
- expectedCode: http.StatusOK,
- },
- {
- name: "Unauthorized_NoSession",
- query: "f=json&attributes=loginId&devId=ao1yOLlHVHhsa3o6&c=_callbacks_._abc",
- checkBody: func(t *testing.T, body string) {
- assert.Contains(t, body, `"statusCode":401`)
- assert.Contains(t, body, `"redirectURL"`)
- },
- expectedCode: http.StatusOK,
- },
- {
- // getToken no longer checks account existence; an unknown screen name
- // still receives a token. Existence is enforced later by
- // RegisterBOSSession during startSession.
- name: "Success_UnknownUserStillIssuesToken",
- query: "f=json&attributes=loginId&devId=dev123&c=_callbacks_._xyz",
- cookies: []*http.Cookie{
- {Name: "localAuthUser", Value: "missing||Missing User"},
- },
- checkBody: func(t *testing.T, body string) {
- assert.Contains(t, body, `"statusCode":200`)
- assert.Contains(t, body, `"loginId":"missing"`)
- assert.Contains(t, body, `"a":`)
- },
- expectedCode: http.StatusOK,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- handler := &AuthHandler{
- AuthService: &testAuthService{},
- CookieBaker: &testCookieBaker{},
- Logger: slog.Default(),
- }
- req, err := http.NewRequest(http.MethodGet, "/auth/getToken?"+tt.query, nil)
- assert.NoError(t, err)
- for _, c := range tt.cookies {
- req.AddCookie(c)
- }
- rr := httptest.NewRecorder()
- handler.GetToken(rr, req)
- assert.Equal(t, tt.expectedCode, rr.Code)
- if tt.checkBody != nil {
- tt.checkBody(t, rr.Body.String())
- }
- })
- }
- }
- func TestAuthHandler_ClientLogin(t *testing.T) {
- tests := []struct {
- name string
- method string
- contentType string
- body string
- auth *testAuthService
- expectedStatusCode int
- checkResponse func(*testing.T, string)
- }{
- {
- name: "Success_JSONBody",
- method: "POST",
- contentType: "application/json",
- body: `{"username":"testuser","password":"testpass","devId":"dev123"}`,
- auth: &testAuthService{
- flapLogin: func(ctx context.Context, inFrame wire.FLAPSignonFrame, advertisedHost string) (wire.TLVRestBlock, error) {
- return successfulLoginBlock(), nil
- },
- },
- expectedStatusCode: http.StatusOK,
- checkResponse: func(t *testing.T, body string) {
- assert.Contains(t, body, `"statusCode":200`)
- assert.Contains(t, body, `"loginId":"testuser"`)
- assert.Contains(t, body, `"screenName":"testuser"`)
- assert.Contains(t, body, `"token"`)
- assert.Contains(t, body, `"sessionSecret"`)
- },
- },
- {
- name: "Success_FormEncoded",
- method: "POST",
- contentType: "application/x-www-form-urlencoded",
- body: "s=testuser&pwd=testpass&devId=dev123",
- auth: &testAuthService{
- flapLogin: func(ctx context.Context, inFrame wire.FLAPSignonFrame, advertisedHost string) (wire.TLVRestBlock, error) {
- return successfulLoginBlock(), nil
- },
- },
- expectedStatusCode: http.StatusOK,
- checkResponse: func(t *testing.T, body string) {
- assert.Contains(t, body, `"statusCode":200`)
- assert.Contains(t, body, `"loginId":"testuser"`)
- },
- },
- {
- name: "Error_MissingUsername",
- method: "POST",
- contentType: "application/json",
- body: `{"username":"","password":"testpass"}`,
- auth: &testAuthService{},
- expectedStatusCode: http.StatusBadRequest,
- checkResponse: func(t *testing.T, body string) {
- assert.Contains(t, body, "username and password required")
- },
- },
- {
- name: "Error_MissingPassword",
- method: "POST",
- contentType: "application/json",
- body: `{"username":"testuser","password":""}`,
- auth: &testAuthService{},
- expectedStatusCode: http.StatusBadRequest,
- checkResponse: func(t *testing.T, body string) {
- assert.Contains(t, body, "username and password required")
- },
- },
- {
- name: "Error_AuthFailed",
- method: "POST",
- contentType: "application/json",
- body: `{"username":"testuser","password":"wrongpass"}`,
- auth: &testAuthService{
- flapLogin: func(ctx context.Context, inFrame wire.FLAPSignonFrame, advertisedHost string) (wire.TLVRestBlock, error) {
- return failedLoginBlock(), nil
- },
- },
- expectedStatusCode: http.StatusUnauthorized,
- checkResponse: func(t *testing.T, body string) {
- assert.Contains(t, body, "username and password required")
- },
- },
- {
- name: "Error_FLAPLoginError",
- method: "POST",
- contentType: "application/json",
- body: `{"username":"testuser","password":"testpass"}`,
- auth: &testAuthService{
- flapLogin: func(ctx context.Context, inFrame wire.FLAPSignonFrame, advertisedHost string) (wire.TLVRestBlock, error) {
- return wire.TLVRestBlock{}, errors.New("boom")
- },
- },
- expectedStatusCode: http.StatusInternalServerError,
- checkResponse: func(t *testing.T, body string) {
- assert.Contains(t, body, "internal server error")
- },
- },
- {
- name: "Error_InvalidJSON",
- method: "POST",
- contentType: "application/json",
- body: `{invalid json`,
- auth: &testAuthService{},
- expectedStatusCode: http.StatusBadRequest,
- checkResponse: func(t *testing.T, body string) {
- assert.Contains(t, body, "invalid JSON format")
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- logger := slog.Default()
- handler := &AuthHandler{
- AuthService: tt.auth,
- Logger: logger,
- }
- req, err := http.NewRequest(tt.method, "/auth/clientLogin", strings.NewReader(tt.body))
- assert.NoError(t, err)
- req.Header.Set("Content-Type", tt.contentType)
- rr := httptest.NewRecorder()
- handler.ClientLogin(rr, req)
- assert.Equal(t, tt.expectedStatusCode, rr.Code)
- responseBody := strings.TrimSpace(rr.Body.String())
- if tt.checkResponse != nil {
- tt.checkResponse(t, responseBody)
- }
- })
- }
- }
|