Skip to content

Commit

Permalink
restructuring the doc
Browse files Browse the repository at this point in the history
  • Loading branch information
or-else committed Jul 16, 2018
1 parent a60b861 commit b0125fa
Show file tree
Hide file tree
Showing 10 changed files with 819 additions and 753 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ The goal of this project is to actually deliver on XMPP's original vision: creat

* For support, general questions, discussions post to [https://groups.google.com/d/forum/tinode](https://groups.google.com/d/forum/tinode).
* For bugs and feature requests [open an issue](https://github.com/tinode/chat/issues/new).
* Read [API documentation](docs/API.md).


## Demo
Expand Down
1,466 changes: 752 additions & 714 deletions docs/API.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions server/auth/anon/auth_anon.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ func (AnonAuth) UpdateRecord(rec *auth.Rec, secret []byte) error {
}

// Authenticate is not supported. It's used only at account creation time.
func (AnonAuth) Authenticate(secret []byte) (*auth.Rec, error) {
return nil, types.ErrUnsupported
func (AnonAuth) Authenticate(secret []byte) (*auth.Rec, []byte, error) {
return nil, nil, types.ErrUnsupported
}

// IsUnique for a noop. Anonymous login does not use secret, any secret is fine.
Expand Down
13 changes: 7 additions & 6 deletions server/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,22 +75,23 @@ type Rec struct {

// AuthHandler is the interface which auth providers must implement.
type AuthHandler interface {
// Init initialize the handler.
// Init initializes the handler.
Init(jsonconf string) error

// AddRecord adds persistent record to database.
// AddRecord adds persistent authentication record to the database.
// Returns: updated auth record, error
AddRecord(rec *Rec, secret []byte) (*Rec, error)

// UpdateRecord updates existing record with new credentials. Returns a numeric error code to indicate
// if the error is due to a duplicate or some other error.
UpdateRecord(rec *Rec, secret []byte) error

// Authenticate: given a user-provided authentication secret (such as "login:password"
// return user ID, time when the secret expires (zero, if never) or an error code.
// Authenticate: given a user-provided authentication secret (such as "login:password"), either
// return user's record (ID, time when the secret expires), or issue a challenge to
// continue the authentication process to the next step, or return an error code.
// store.Users.GetAuthRecord("scheme", "unique")
// Returns: user auth record, error.
Authenticate(secret []byte) (*Rec, error)
// Returns: user auth record, challenge, error.
Authenticate(secret []byte) (*Rec, []byte, error)

// IsUnique verifies if the provided secret can be considered unique by the auth scheme
// E.g. if login is unique.
Expand Down
16 changes: 8 additions & 8 deletions server/auth/basic/auth_basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,33 +123,33 @@ func (authenticator) UpdateRecord(rec *auth.Rec, secret []byte) error {
}

// Authenticate checks login and password.
func (authenticator) Authenticate(secret []byte) (*auth.Rec, error) {
func (authenticator) Authenticate(secret []byte) (*auth.Rec, []byte, error) {
uname, password, err := parseSecret(secret)
if err != nil {
return nil, err
return nil, nil, err
}

if len([]rune(uname)) < minLoginLength || len([]rune(uname)) > maxLoginLength {
return nil, types.ErrFailed
return nil, nil, types.ErrFailed
}

uid, authLvl, passhash, expires, err := store.Users.GetAuthUniqueRecord("basic", uname)
if err != nil {
return nil, err
return nil, nil, err
}
if uid.IsZero() {
// Invalid login.
return nil, types.ErrFailed
return nil, nil, types.ErrFailed
}
if !expires.IsZero() && expires.Before(time.Now()) {
// The record has expired
return nil, types.ErrExpired
return nil, nil, types.ErrExpired
}

err = bcrypt.CompareHashAndPassword([]byte(passhash), []byte(password))
if err != nil {
// Invalid password
return nil, types.ErrFailed
return nil, nil, types.ErrFailed
}

var lifetime time.Duration
Expand All @@ -160,7 +160,7 @@ func (authenticator) Authenticate(secret []byte) (*auth.Rec, error) {
Uid: uid,
AuthLevel: authLvl,
Lifetime: lifetime,
Features: 0}, nil
Features: 0}, nil, nil
}

// IsUnique checks login uniqueness.
Expand Down
16 changes: 8 additions & 8 deletions server/auth/token/auth_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,18 +80,18 @@ func (authenticator) UpdateRecord(rec *auth.Rec, secret []byte) error {
}

// Authenticate checks validity of provided token.
func (ta *authenticator) Authenticate(token []byte) (*auth.Rec, error) {
func (ta *authenticator) Authenticate(token []byte) (*auth.Rec, []byte, error) {
var tl tokenLayout
dataSize := binary.Size(&tl)
if len(token) < dataSize+sha256.Size {
// Token is too short
return nil, types.ErrMalformed
return nil, nil, types.ErrMalformed
}

buf := bytes.NewBuffer(token)
err := binary.Read(buf, binary.LittleEndian, &tl)
if err != nil {
return nil, types.ErrMalformed
return nil, nil, types.ErrMalformed
}

hbuf := new(bytes.Buffer)
Expand All @@ -100,27 +100,27 @@ func (ta *authenticator) Authenticate(token []byte) (*auth.Rec, error) {
hasher := hmac.New(sha256.New, ta.hmacSalt)
hasher.Write(hbuf.Bytes())
if !hmac.Equal(token[dataSize:dataSize+sha256.Size], hasher.Sum(nil)) {
return nil, types.ErrFailed
return nil, nil, types.ErrFailed
}

if tl.AuthLevel < 0 || auth.Level(tl.AuthLevel) > auth.LevelRoot {
return nil, types.ErrMalformed
return nil, nil, types.ErrMalformed
}

if int(tl.SerialNumber) != ta.serialNumber {
return nil, types.ErrMalformed
return nil, nil, types.ErrMalformed
}

expires := time.Unix(int64(tl.Expires), 0).UTC()
if expires.Before(time.Now().Add(1 * time.Second)) {
return nil, types.ErrExpired
return nil, nil, types.ErrExpired
}

return &auth.Rec{
Uid: types.Uid(tl.Uid),
AuthLevel: auth.Level(tl.AuthLevel),
Lifetime: time.Until(expires),
Features: auth.Feature(tl.Features)}, nil
Features: auth.Feature(tl.Features)}, nil, nil
}

// GenSecret generates a new token.
Expand Down
10 changes: 10 additions & 0 deletions server/datamodel.go
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,16 @@ func InfoValidateCredentials(id string, ts time.Time) *ServerComMessage {
Timestamp: ts}}
}

// InfoChallenge requires user to respond to presented challenge before login can be completed (300).
func InfoChallenge(id string, ts time.Time, challenge []byte) *ServerComMessage {
return &ServerComMessage{Ctrl: &MsgServerCtrl{
Id: id,
Code: http.StatusMultipleChoices, // 300
Text: "challenge",
Params: map[string]interface{}{"challenge": challenge},
Timestamp: ts}}
}

// InfoAlreadySubscribed request to subscribe was ignored because user is already subscribed (304).
func InfoAlreadySubscribed(id, topic string, ts time.Time) *ServerComMessage {
return &ServerComMessage{Ctrl: &MsgServerCtrl{
Expand Down
16 changes: 12 additions & 4 deletions server/hdl_files.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,16 @@ func largeFileServe(wrt http.ResponseWriter, req *http.Request) {
return
}

// Check authorization: either the token or SID must be present
uid, err := authHttpRequest(req)
// Check authorization: either auth information or SID must be present
uid, challenge, err := authHttpRequest(req)
if err != nil {
writeHttpResponse(decodeStoreError(err, "", "", now, nil))
return
}
if challenge != nil {
writeHttpResponse(InfoChallenge("", now, challenge))
return
}
if uid.IsZero() {
// Not authenticated
writeHttpResponse(ErrAuthRequired("", "", now))
Expand Down Expand Up @@ -123,12 +127,16 @@ func largeFileUpload(wrt http.ResponseWriter, req *http.Request) {
writeHttpResponse(ErrAPIKeyRequired(now))
return
}
// Check authorization: either the token or SID must be present
uid, err := authHttpRequest(req)
// Check authorization: either auth information or SID must be present
uid, challenge, err := authHttpRequest(req)
if err != nil {
writeHttpResponse(decodeStoreError(err, "", "", now, nil))
return
}
if challenge != nil {
writeHttpResponse(InfoChallenge("", now, challenge))
return
}
if uid.IsZero() {
// Not authenticated
writeHttpResponse(ErrAuthRequired("", "", now))
Expand Down
22 changes: 12 additions & 10 deletions server/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,22 +322,24 @@ func getHttpAuth(req *http.Request) (string, string) {
}

// Authenticate non-websocket HTTP request
func authHttpRequest(req *http.Request) (types.Uid, error) {
func authHttpRequest(req *http.Request) (types.Uid, []byte, error) {
var uid types.Uid
if authMethod, secret := getHttpAuth(req); authMethod != "" {
decodedSecret := make([]byte, base64.StdEncoding.DecodedLen(len(secret)))
if _, err := base64.StdEncoding.Decode(decodedSecret, []byte(secret)); err != nil {
return uid, types.ErrMalformed
return uid, nil, types.ErrMalformed
}
authhdl := store.GetAuthHandler(authMethod)
if authhdl != nil {
if rec, err := authhdl.Authenticate(decodedSecret); err == nil {
uid = rec.Uid
} else {
return uid, err
if authhdl := store.GetAuthHandler(authMethod); authhdl != nil {
rec, challenge, err := authhdl.Authenticate(decodedSecret)
if err != nil {
return uid, nil, err
}
if challenge != nil {
return uid, challenge, nil
}
uid = rec.Uid
} else {
log.Println("fileUpload: token is present but token auth handler is not found")
log.Println("fileUpload: auth data is present but handler is not found", authMethod)
}
} else {
// Find the session, make sure it's appropriately authenticated.
Expand All @@ -346,5 +348,5 @@ func authHttpRequest(req *http.Request) (types.Uid, error) {
uid = sess.uid
}
}
return uid, nil
return uid, nil, nil
}
8 changes: 7 additions & 1 deletion server/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -655,12 +655,18 @@ func (s *Session) login(msg *ClientComMessage) {
return
}

rec, err := handler.Authenticate(msg.Login.Secret)
rec, challenge, err := handler.Authenticate(msg.Login.Secret)
if err != nil {
s.queueOut(decodeStoreError(err, msg.Login.Id, "", msg.timestamp, nil))
return
}

if challenge != nil {
reply := NoErr(msg.Login.Id, "", msg.timestamp)
s.queueOut(reply)
return
}

var missing []string
if rec.Features&auth.Validated == 0 {
missing, err = s.getValidatedGred(rec.Uid, rec.AuthLevel, msg.Login.Cred)
Expand Down

0 comments on commit b0125fa

Please sign in to comment.