From 56aa603293f70f99c832feeef47c254ea7edde04 Mon Sep 17 00:00:00 2001 From: Lance Cooper Date: Tue, 7 Apr 2015 21:38:18 -0500 Subject: [PATCH] Add guards for NPE's when processing channelling API messages. This also refactors the channelling API for better clarity as well as introducing the following functional changes: * All Hello requests will now receive a Welcome or Error return. * The message sent in response to a successful authentication request now uses the Iid provided with the originating request. * Responses to requests for the Self document now use the Iid provided with the originating request. * Failed authentication requests now receive an error return. * Malformed requests for the Hello, Authentication, Sessions, and Rooms documents will now receive an error return. * Requests for the Sessions document which cannot be processed now receive an error return. * Requests for the Users document will receive an error return if the requesting session has not joined a room. --- doc/CHANNELING-API.txt | 37 ++- .../spreed-webrtc-server/channelling_api.go | 313 +++++++++++------- .../channelling_api_test.go | 81 ++--- src/app/spreed-webrtc-server/client.go | 31 +- src/app/spreed-webrtc-server/session.go | 16 +- 5 files changed, 280 insertions(+), 198 deletions(-) diff --git a/doc/CHANNELING-API.txt b/doc/CHANNELING-API.txt index 4427ec7a..0d328ad9 100644 --- a/doc/CHANNELING-API.txt +++ b/doc/CHANNELING-API.txt @@ -98,6 +98,13 @@ Error returns "Message": "A description of the error condition" } + The following predefined error codes may implicitly be returned by any call + which returns an error document: + + unknown: An internal server error, the message may provide more information. + bad_request: The structure or content of the client's request was invalid, + the message may contain specifics. + Special purpose documents for channling Self @@ -427,9 +434,10 @@ Additional types for session listing and notifications Rev is the status update sequence for this status update entry. It is a positive integer. Higher numbers are later status updates. - When the current session is in a room (means sent Hello), a Users request - can be sent, to receive a list of sessions in that particular room. This - always returns the sessions in the same room as the calling session. + When the current session has successfully joined a room (see Hello for more + details), a Users request will return a Users document containing session + details for the current room. An Error document will be returned if no room + has been joined or session information cannot be retrieved. Users (Request uses empty data) @@ -470,6 +478,10 @@ Additional types for session listing and notifications Note: The Userid field is only present, if that session belongs to a known user. + Error codes: + + not_in_room: Clients must join a room before requesting users. + Alive { @@ -506,12 +518,19 @@ User authorization and session authentication The Authentication document binds a userid to the current session. The Nonce and Userid need to be validateable by the server. If Authentication - was successfull, a new Self document will be sent. The Nonce value can - be generated by using the REST API (sessions end point). + was successful, a new Self document will be sent. Otherwise an Error + document will be returned describing why authentication failed. Note that + the Nonce value can be generated by using the REST API (sessions end point). There is no way to undo authentication for a session. For log out, close the session (disconnect) and forget the token. + Error codes: + + already_authenticated: This session has already authenticated, follow + the reauthentication procedure above. + invalid_session_token: The provided session token information is invalid, + the error message may contain more information. Information retrieval @@ -532,6 +551,9 @@ Information retrieval Token data retrieved on incoming messages as A field (attestation token). + If session information retrieval fails, an Error document with one of the + listed codes will be returned. + Sessions (Response with Id, Token and Type from request and populated Session list). @@ -558,6 +580,11 @@ Information retrieval ] } + Error codes: + + contacts_not_enabled: Requests with subtype `contact` are not enabled. + bad_attestation: The requested session attestation is invalid. + no_such_session: The requested session could not be found. Chat messages and status information diff --git a/src/app/spreed-webrtc-server/channelling_api.go b/src/app/spreed-webrtc-server/channelling_api.go index 8c42dab1..200b1daa 100644 --- a/src/app/spreed-webrtc-server/channelling_api.go +++ b/src/app/spreed-webrtc-server/channelling_api.go @@ -31,9 +31,9 @@ const ( ) type ChannellingAPI interface { - OnConnect(Client, *Session) + OnConnect(Client, *Session) (interface{}, error) OnDisconnect(Client, *Session) - OnIncoming(ResponseSender, *Session, *DataIncoming) + OnIncoming(Sender, *Session, *DataIncoming) (interface{}, error) } type channellingAPI struct { @@ -60,169 +60,113 @@ func NewChannellingAPI(config *Config, roomStatus RoomStatusManager, sessionEnco } } -func (api *channellingAPI) OnConnect(client Client, session *Session) { +func (api *channellingAPI) OnConnect(client Client, session *Session) (interface{}, error) { api.Unicaster.OnConnect(client, session) - api.SendSelf(client, session) + return api.MakeSelf(session) } func (api *channellingAPI) OnDisconnect(client Client, session *Session) { api.Unicaster.OnDisconnect(client, session) } -func (api *channellingAPI) OnIncoming(c ResponseSender, session *Session, msg *DataIncoming) { +func (api *channellingAPI) OnIncoming(sender Sender, session *Session, msg *DataIncoming) (interface{}, error) { switch msg.Type { case "Self": - api.SendSelf(c, session) + return api.MakeSelf(session) case "Hello": - //log.Println("Hello", msg.Hello, c.Index()) - // TODO(longsleep): Filter room id and user agent. - session.Update(&SessionUpdate{Types: []string{"Ua"}, Ua: msg.Hello.Ua}) - - room, err := session.JoinRoom(msg.Hello.Id, msg.Hello.Credentials, c) - // NOTE(lcooper): Iid filtered for compatibility's sake. - // Evaluate sending unconditionally when supported by all clients. - if msg.Iid != "" { - if err == nil { - c.Reply(msg.Iid, &DataWelcome{ - Type: "Welcome", - Room: room, - Users: api.RoomUsers(session), - }) - } else { - c.Reply(msg.Iid, err) - } + if msg.Hello == nil { + return nil, NewDataError("bad_request", "message did not contain Hello") } + + return api.HandleHello(session, msg.Hello, sender) case "Offer": + if msg.Offer == nil { + log.Println("Received invalid offer message.", msg) + break + } + // TODO(longsleep): Validate offer session.Unicast(msg.Offer.To, msg.Offer) case "Candidate": + if msg.Candidate == nil { + log.Println("Received invalid candidate message.", msg) + break + } + // TODO(longsleep): Validate candidate session.Unicast(msg.Candidate.To, msg.Candidate) case "Answer": + if msg.Answer == nil { + log.Println("Received invalid answer message.", msg) + break + } + // TODO(longsleep): Validate Answer session.Unicast(msg.Answer.To, msg.Answer) case "Users": - if session.Hello { - sessions := &DataSessions{Type: "Users", Users: api.RoomUsers(session)} - c.Reply(msg.Iid, sessions) - } + return api.HandleUsers(session) case "Authentication": - st := msg.Authentication.Authentication - if st == nil { - return + if msg.Authentication == nil || msg.Authentication.Authentication == nil { + return nil, NewDataError("bad_request", "message did not contain Authentication") } - if err := api.Authenticate(session, st, ""); err == nil { - log.Println("Authentication success", session.Userid()) - api.SendSelf(c, session) - session.BroadcastStatus() - } else { - log.Println("Authentication failed", err, st.Userid, st.Nonce) - } + return api.HandleAuthentication(session, msg.Authentication.Authentication) case "Bye": + if msg.Bye == nil { + log.Println("Received invalid bye message.", msg) + break + } + session.Unicast(msg.Bye.To, msg.Bye) case "Status": + if msg.Status == nil { + log.Println("Received invalid status message.", msg) + break + } + //log.Println("Status", msg.Status) session.Update(&SessionUpdate{Types: []string{"Status"}, Status: msg.Status.Status}) session.BroadcastStatus() case "Chat": - // TODO(longsleep): Limit sent chat messages per incoming connection. - if !msg.Chat.Chat.NoEcho { - session.Unicast(session.Id, msg.Chat) - } - msg.Chat.Chat.Time = time.Now().Format(time.RFC3339) - if msg.Chat.To == "" { - // TODO(longsleep): Check if chat broadcast is allowed. - if session.Hello { - api.CountBroadcastChat() - session.Broadcast(msg.Chat) - } - } else { - if msg.Chat.Chat.Status != nil && msg.Chat.Chat.Status.ContactRequest != nil { - if !api.Config.WithModule("contacts") { - return - } - if err := api.contactrequestHandler(session, msg.Chat.To, msg.Chat.Chat.Status.ContactRequest); err != nil { - log.Println("Ignoring invalid contact request.", err) - return - } - msg.Chat.Chat.Status.ContactRequest.Userid = session.Userid() - } - if msg.Chat.Chat.Status == nil { - api.CountUnicastChat() - } - - session.Unicast(msg.Chat.To, msg.Chat) - if msg.Chat.Chat.Mid != "" { - // Send out delivery confirmation status chat message. - session.Unicast(session.Id, &DataChat{To: msg.Chat.To, Type: "Chat", Chat: &DataChatMessage{Mid: msg.Chat.Chat.Mid, Status: &DataChatStatus{State: "sent"}}}) - } + if msg.Chat == nil || msg.Chat.Chat == nil { + log.Println("Received invalid chat message.", msg) + break } + + api.HandleChat(session, msg.Chat.To, msg.Chat.Chat) case "Conference": - // Check conference maximum size. - if len(msg.Conference.Conference) > maxConferenceSize { - log.Println("Refusing to create conference above limit.", len(msg.Conference.Conference)) - } else { - // Send conference update to anyone. - for _, id := range msg.Conference.Conference { - if id != session.Id { - session.Unicast(id, msg.Conference) - } - } + if msg.Conference == nil { + log.Println("Received invalid conference message.", msg) + break } + + api.HandleConference(session, msg.Conference) case "Alive": - c.Reply(msg.Iid, msg.Alive) + return msg.Alive, nil case "Sessions": - var users []*DataSession - switch msg.Sessions.Sessions.Type { - case "contact": - if api.Config.WithModule("contacts") { - if userID, err := api.getContactID(session, msg.Sessions.Sessions.Token); err == nil { - users = api.GetUserSessions(session, userID) - } else { - log.Printf(err.Error()) - } - } else { - log.Printf("Incoming contacts session request with contacts disabled") - } - case "session": - id, err := session.attestation.Decode(msg.Sessions.Sessions.Token) - if err != nil { - log.Printf("Failed to decode incoming attestation", err, msg.Sessions.Sessions.Token) - break - } - session, ok := api.GetSession(id) - if !ok { - log.Printf("Cannot retrieve session for id %s", id) - break - } - users = make([]*DataSession, 1, 1) - users[0] = session.Data() - default: - log.Printf("Unkown incoming sessions request type %s", msg.Sessions.Sessions.Type) + if msg.Sessions == nil || msg.Sessions.Sessions == nil { + return nil, NewDataError("bad_request", "message did not contain Sessions") } - // TODO(lcooper): We ought to reply with a *DataError here if failed. - if users != nil { - c.Reply(msg.Iid, &DataSessions{Type: "Sessions", Users: users, Sessions: msg.Sessions.Sessions}) - } + return api.HandleSessions(session, msg.Sessions.Sessions) case "Room": - if room, err := api.UpdateRoom(session, msg.Room); err == nil { - session.Broadcast(room) - c.Reply(msg.Iid, room) - } else { - c.Reply(msg.Iid, err) + if msg.Room == nil { + return nil, NewDataError("bad_request", "message did not contain Room") } + + return api.HandleRoom(session, msg.Room) default: log.Println("OnText unhandled message type", msg.Type) } + + return nil, nil } -func (api *channellingAPI) SendSelf(c Responder, session *Session) { +func (api *channellingAPI) MakeSelf(session *Session) (*DataSelf, error) { token, err := api.EncodeSessionToken(session) if err != nil { log.Println("Error in OnRegister", err) - return + return nil, err } log.Println("Created new session token", len(token), token) @@ -237,5 +181,138 @@ func (api *channellingAPI) SendSelf(c Responder, session *Session) { Turn: api.CreateTurnData(session), Stun: api.StunURIs, } - c.Reply("", self) + + return self, nil +} + +func (api *channellingAPI) HandleHello(session *Session, hello *DataHello, sender Sender) (*DataWelcome, error) { + // TODO(longsleep): Filter room id and user agent. + session.Update(&SessionUpdate{Types: []string{"Ua"}, Ua: hello.Ua}) + + room, err := session.JoinRoom(hello.Id, hello.Credentials, sender) + if err != nil { + return nil, err + } + return &DataWelcome{ + Type: "Welcome", + Room: room, + Users: api.RoomUsers(session), + }, nil +} + +func (api *channellingAPI) HandleUsers(session *Session) (sessions *DataSessions, err error) { + if session.Hello { + sessions = &DataSessions{Type: "Users", Users: api.RoomUsers(session)} + } else { + err = NewDataError("not_in_room", "Cannot list users without a current room") + } + return +} + +func (api *channellingAPI) HandleAuthentication(session *Session, st *SessionToken) (*DataSelf, error) { + if err := api.Authenticate(session, st, ""); err != nil { + log.Println("Authentication failed", err, st.Userid, st.Nonce) + return nil, err + } + + log.Println("Authentication success", session.Userid()) + self, err := api.MakeSelf(session) + if err == nil { + session.BroadcastStatus() + } + + return self, err +} + +func (api *channellingAPI) HandleChat(session *Session, to string, chat *DataChatMessage) { + // TODO(longsleep): Limit sent chat messages per incoming connection. + if !chat.NoEcho { + session.Unicast(session.Id, chat) + } + chat.Time = time.Now().Format(time.RFC3339) + if to == "" { + // TODO(longsleep): Check if chat broadcast is allowed. + if session.Hello { + api.CountBroadcastChat() + session.Broadcast(chat) + } + } else { + if chat.Status != nil { + if chat.Status.ContactRequest != nil { + if !api.Config.WithModule("contacts") { + return + } + if err := api.contactrequestHandler(session, to, chat.Status.ContactRequest); err != nil { + log.Println("Ignoring invalid contact request.", err) + return + } + chat.Status.ContactRequest.Userid = session.Userid() + } + } else { + api.CountUnicastChat() + } + + session.Unicast(to, chat) + if chat.Mid != "" { + // Send out delivery confirmation status chat message. + session.Unicast(session.Id, &DataChat{To: to, Type: "Chat", Chat: &DataChatMessage{Mid: chat.Mid, Status: &DataChatStatus{State: "sent"}}}) + } + } +} + +func (api *channellingAPI) HandleConference(session *Session, conference *DataConference) { + // Check conference maximum size. + if len(conference.Conference) > maxConferenceSize { + log.Println("Refusing to create conference above limit.", len(conference.Conference)) + return + } + + // Send conference update to anyone. + for _, id := range conference.Conference { + if id != session.Id { + session.Unicast(id, conference) + } + } +} + +func (api *channellingAPI) HandleSessions(session *Session, sessions *DataSessionsRequest) (*DataSessions, error) { + switch sessions.Type { + case "contact": + if !api.Config.WithModule("contacts") { + return nil, NewDataError("contacts_not_enabled", "incoming contacts session request with contacts disabled") + } + userID, err := api.getContactID(session, sessions.Token) + if err != nil { + return nil, err + } + return &DataSessions{ + Type: "Sessions", + Users: api.GetUserSessions(session, userID), + Sessions: sessions, + }, nil + case "session": + id, err := session.attestation.Decode(sessions.Token) + if err != nil { + return nil, NewDataError("bad_attestation", err.Error()) + } + session, ok := api.GetSession(id) + if !ok { + return nil, NewDataError("no_such_session", "cannot retrieve session") + } + return &DataSessions{ + Type: "Sessions", + Users: []*DataSession{session.Data()}, + Sessions: sessions, + }, nil + default: + return nil, NewDataError("bad_request", "unknown sessions request type") + } +} + +func (api *channellingAPI) HandleRoom(session *Session, room *DataRoom) (*DataRoom, error) { + room, err := api.UpdateRoom(session, room) + if err == nil { + session.Broadcast(room) + } + return room, err } diff --git a/src/app/spreed-webrtc-server/channelling_api_test.go b/src/app/spreed-webrtc-server/channelling_api_test.go index ebf004fd..df91e779 100644 --- a/src/app/spreed-webrtc-server/channelling_api_test.go +++ b/src/app/spreed-webrtc-server/channelling_api_test.go @@ -27,20 +27,11 @@ import ( ) type fakeClient struct { - replies map[string]interface{} } func (fake *fakeClient) Send(_ Buffer) { } -func (fake *fakeClient) Reply(iid string, msg interface{}) { - if fake.replies == nil { - fake.replies = make(map[string]interface{}) - } - - fake.replies[iid] = msg -} - type fakeRoomManager struct { joinedRoomID string leftRoomID string @@ -74,29 +65,6 @@ func (fake *fakeRoomManager) UpdateRoom(_ *Session, _ *DataRoom) (*DataRoom, err return fake.updatedRoom, fake.updateError } -func assertReply(t *testing.T, client *fakeClient, iid string) interface{} { - msg, ok := client.replies[iid] - if !ok { - t.Fatalf("No response received for Iid %v", iid) - } - return msg -} - -func assertErrorReply(t *testing.T, client *fakeClient, iid, code string) { - err, ok := assertReply(t, client, iid).(*DataError) - if !ok { - t.Fatalf("Expected response message to be an Error") - } - - if err.Type != "Error" { - t.Error("Message did not have the correct type") - } - - if err.Code != code { - t.Errorf("Expected error code to be %v, but was %v", code, err.Code) - } -} - func NewTestChannellingAPI() (ChannellingAPI, *fakeClient, *Session, *fakeRoomManager) { client, roomManager := &fakeClient{}, &fakeRoomManager{} session := &Session{ @@ -168,21 +136,19 @@ func Test_ChannellingAPI_OnIncoming_HelloMessage_DoesNotJoinIfNotPermitted(t *te } } -func Test_ChannellingAPI_OnIncoming_HelloMessageWithAnIid_RespondsWithAWelcome(t *testing.T) { - iid, roomID := "foo", "a-room" +func Test_ChannellingAPI_OnIncoming_HelloMessage_RespondsWithAWelcome(t *testing.T) { + roomID := "a-room" api, client, session, roomManager := NewTestChannellingAPI() roomManager.roomUsers = []*DataSession{&DataSession{}} - api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Iid: iid, Hello: &DataHello{Id: roomID}}) - - msg, ok := client.replies[iid] - if !ok { - t.Fatalf("No response received for Iid %v", iid) + reply, err := api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: roomID}}) + if err != nil { + t.Fatalf("Unexpected error %v", err) } - welcome, ok := msg.(*DataWelcome) + welcome, ok := reply.(*DataWelcome) if !ok { - t.Fatalf("Expected response message %#v to be a Welcome", msg) + t.Fatalf("Expected response %#v to be a Welcome", reply) } if welcome.Type != "Welcome" { @@ -198,25 +164,31 @@ func Test_ChannellingAPI_OnIncoming_HelloMessageWithAnIid_RespondsWithAWelcome(t } } -func Test_ChannellingAPI_OnIncoming_HelloMessageWithAnIid_RespondsWithAnErrorIfTheRoomCannotBeJoined(t *testing.T) { - iid := "foo" +func Test_ChannellingAPI_OnIncoming_HelloMessage_RespondsWithAnErrorIfTheRoomCannotBeJoined(t *testing.T) { api, client, session, roomManager := NewTestChannellingAPI() roomManager.joinError = NewDataError("bad_join", "") - api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Iid: iid, Hello: &DataHello{}}) + _, err := api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{}}) - assertErrorReply(t, client, iid, "bad_join") + assertDataError(t, err, "bad_join") } func Test_ChannellingAPI_OnIncoming_RoomMessage_RespondsWithAndBroadcastsTheUpdatedRoom(t *testing.T) { - iid, roomName := "123", "foo" + roomName := "foo" api, client, session, roomManager := NewTestChannellingAPI() roomManager.updatedRoom = &DataRoom{Name: "FOO"} - api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Iid: "0", Hello: &DataHello{Id: roomName}}) - api.OnIncoming(client, session, &DataIncoming{Type: "Room", Iid: iid, Room: &DataRoom{Name: roomName}}) + _, err := api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: roomName}}) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } - room, ok := assertReply(t, client, iid).(*DataRoom) + reply, err := api.OnIncoming(client, session, &DataIncoming{Type: "Room", Room: &DataRoom{Name: roomName}}) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + + room, ok := reply.(*DataRoom) if !ok { t.Fatalf("Expected response message to be a Room") } @@ -235,12 +207,15 @@ func Test_ChannellingAPI_OnIncoming_RoomMessage_RespondsWithAndBroadcastsTheUpda } func Test_ChannellingAPI_OnIncoming_RoomMessage_RespondsWithAnErrorIfUpdatingTheRoomFails(t *testing.T) { - iid, roomName := "123", "foo" + roomName := "foo" api, client, session, roomManager := NewTestChannellingAPI() roomManager.updateError = NewDataError("a_room_error", "") - api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Iid: "0", Hello: &DataHello{Id: roomName}}) - api.OnIncoming(client, session, &DataIncoming{Type: "Room", Iid: iid, Room: &DataRoom{Name: roomName}}) + _, err := api.OnIncoming(client, session, &DataIncoming{Type: "Hello", Hello: &DataHello{Id: roomName}}) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + _, err = api.OnIncoming(client, session, &DataIncoming{Type: "Room", Room: &DataRoom{Name: roomName}}) - assertErrorReply(t, client, iid, "a_room_error") + assertDataError(t, err, "a_room_error") } diff --git a/src/app/spreed-webrtc-server/client.go b/src/app/spreed-webrtc-server/client.go index b858176b..85c0f062 100644 --- a/src/app/spreed-webrtc-server/client.go +++ b/src/app/spreed-webrtc-server/client.go @@ -29,17 +29,8 @@ type Sender interface { Send(Buffer) } -type ResponseSender interface { - Sender - Responder -} - -type Responder interface { - Reply(iid string, m interface{}) -} - type Client interface { - ResponseSender + Sender Session() *Session Index() uint64 Close() @@ -59,7 +50,11 @@ func NewClient(codec Codec, api ChannellingAPI, session *Session) *client { func (client *client) OnConnect(conn Connection) { client.Connection = conn - client.ChannellingAPI.OnConnect(client, client.session) + if reply, err := client.ChannellingAPI.OnConnect(client, client.session); err == nil { + client.reply("", reply) + } else { + log.Println("OnConnect error", err) + } } func (client *client) OnDisconnect() { @@ -68,14 +63,20 @@ func (client *client) OnDisconnect() { } func (client *client) OnText(b Buffer) { - if incoming, err := client.DecodeIncoming(b); err == nil { - client.OnIncoming(client, client.session, incoming) - } else { + incoming, err := client.DecodeIncoming(b) + if err != nil { log.Println("OnText error while processing incoming message", err) + return + } + + if reply, err := client.OnIncoming(client, client.session, incoming); err != nil { + client.reply(incoming.Iid, err) + } else if reply != nil { + client.reply(incoming.Iid, reply) } } -func (client *client) Reply(iid string, m interface{}) { +func (client *client) reply(iid string, m interface{}) { outgoing := &DataOutgoing{From: client.session.Id, Iid: iid, Data: m} if b, err := client.EncodeOutgoing(outgoing); err == nil { client.Send(b) diff --git a/src/app/spreed-webrtc-server/session.go b/src/app/spreed-webrtc-server/session.go index c226e6c0..aa07e332 100644 --- a/src/app/spreed-webrtc-server/session.go +++ b/src/app/spreed-webrtc-server/session.go @@ -22,7 +22,6 @@ package main import ( - "errors" "fmt" "github.com/gorilla/securecookie" "strings" @@ -310,15 +309,18 @@ func (s *Session) Authorize(realm string, st *SessionToken) (string, error) { defer s.mutex.Unlock() if s.Id != st.Id || s.Sid != st.Sid { - return "", errors.New("session id mismatch") + return "", NewDataError("invalid_session_token", "session id mismatch") } if s.userid != "" { - return "", errors.New("session already authenticated") + return "", NewDataError("already_authenticated", "session already authenticated") } // Create authentication nonce. var err error s.Nonce, err = sessionNonces.Encode(fmt.Sprintf("%s@%s", s.Sid, realm), st.Userid) + if err != nil { + err = NewDataError("unknown", err.Error()) + } return s.Nonce, err @@ -330,18 +332,18 @@ func (s *Session) Authenticate(realm string, st *SessionToken, userid string) er defer s.mutex.Unlock() if s.userid != "" { - return errors.New("session already authenticated") + return NewDataError("already_authenticated", "session already authenticated") } if userid == "" { if s.Nonce == "" || s.Nonce != st.Nonce { - return errors.New("nonce validation failed") + return NewDataError("invalid_session_token", "nonce validation failed") } err := sessionNonces.Decode(fmt.Sprintf("%s@%s", s.Sid, realm), st.Nonce, &userid) if err != nil { - return err + return NewDataError("invalid_session_token", err.Error()) } if st.Userid != userid { - return errors.New("user id mismatch") + return NewDataError("invalid_session_token", "user id mismatch") } s.Nonce = "" }