mirror of
https://github.com/matrix-org/dendrite.git
synced 2025-12-23 14:53:10 -06:00
210 lines
6.5 KiB
Go
210 lines
6.5 KiB
Go
// Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package auth
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/matrix-org/dendrite/clientapi/jsonerror"
|
|
"github.com/matrix-org/dendrite/clientapi/userutil"
|
|
"github.com/matrix-org/dendrite/internal/config"
|
|
"github.com/matrix-org/dendrite/userapi/api"
|
|
"github.com/matrix-org/gomatrixserverlib"
|
|
"github.com/matrix-org/util"
|
|
)
|
|
|
|
// This file handles all the m.login.token logic
|
|
|
|
// GetAccountByLocalpart function implemented by the appropriate database type
|
|
type GetAccountByLocalpart func(ctx context.Context, localpart string) (*api.Account, error)
|
|
|
|
// LoginTokenRequest struct to hold the possible parameters from an m.login.token http request
|
|
type LoginTokenRequest struct {
|
|
Login
|
|
Token string `json:"token"`
|
|
TxnID string `json:"txn_id"`
|
|
}
|
|
|
|
// LoginTypeToken holds the configs and the appropriate GetAccountByLocalpart function for the database
|
|
type LoginTypeToken struct {
|
|
GetAccountByLocalpart GetAccountByLocalpart
|
|
Config *config.ClientAPI
|
|
}
|
|
|
|
// Name returns the expected type of "m.login.token"
|
|
func (t *LoginTypeToken) Name() string {
|
|
return "m.login.token"
|
|
}
|
|
|
|
// Request returns a struct of type LoginTokenRequest
|
|
func (t *LoginTypeToken) Request() interface{} {
|
|
return &LoginTokenRequest{}
|
|
}
|
|
|
|
// Type of the LoginToken
|
|
type loginToken struct {
|
|
UserID string
|
|
CreationTime int64
|
|
RandomPart string
|
|
}
|
|
|
|
// Login completes the whole token validation, user verification for m.login.token
|
|
// returns a struct of type *auth.Login which has the users details
|
|
func (t *LoginTypeToken) Login(ctx context.Context, req interface{}) (*Login, *util.JSONResponse) {
|
|
r := req.(*LoginTokenRequest)
|
|
userID, err := validateLoginToken(r.Token, r.TxnID, &t.Config.Matrix.ServerName)
|
|
if err != nil {
|
|
return nil, &util.JSONResponse{
|
|
Code: http.StatusUnauthorized,
|
|
JSON: jsonerror.InvalidArgumentValue(err.Error()),
|
|
}
|
|
}
|
|
r.Login.Identifier.User = userID
|
|
r.Login.Identifier.Type = "m.id.user"
|
|
|
|
return &r.Login, nil
|
|
}
|
|
|
|
// Decodes and validates a LoginToken
|
|
// Accepts the base64 encoded token string as param
|
|
// Checks the time expiry, userID (only the format, doesn't check to see if the user exists)
|
|
// Also checks the DB to see if the token exists
|
|
// Returns the localpart if successful
|
|
func validateLoginToken(tokenStr string, txnID string, serverName *gomatrixserverlib.ServerName) (string, error) {
|
|
token, err := decodeLoginToken(tokenStr)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// check whether the token has a valid time.
|
|
// TODO: should this 5 second window be configurable?
|
|
if time.Now().Unix()-token.CreationTime > 5 {
|
|
return "", errors.New("Token has expired")
|
|
}
|
|
|
|
// check whether the UserID is malformed
|
|
if !strings.Contains(token.UserID, "@") {
|
|
// TODO: should we reveal details about the error with the token or give vague responses instead?
|
|
return "", errors.New("Invalid UserID")
|
|
}
|
|
if _, err := userutil.ParseUsernameParam(token.UserID, serverName); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// check in the database
|
|
if err := checkDBToken(tokenStr, txnID); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return token.UserID, nil
|
|
}
|
|
|
|
// GenerateLoginToken creates a login token which is a base64 encoded string of (userID+time+random)
|
|
// returns an error if it cannot create a random string
|
|
func GenerateLoginToken(userID string) (string, error) {
|
|
// the time of token creation
|
|
timePart := []byte(strconv.FormatInt(time.Now().Unix(), 10))
|
|
|
|
// the random part of the token
|
|
randPart := make([]byte, 10)
|
|
if _, err := rand.Read(randPart); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// url-safe no padding
|
|
return base64.RawURLEncoding.EncodeToString([]byte(userID)) + "." + base64.RawURLEncoding.EncodeToString(timePart) + "." + base64.RawURLEncoding.EncodeToString(randPart), nil
|
|
}
|
|
|
|
// Decodes the given tokenStr into a LoginToken struct
|
|
func decodeLoginToken(tokenStr string) (*loginToken, error) {
|
|
// split the string into it's constituent parts
|
|
strParts := strings.Split(tokenStr, ".")
|
|
if len(strParts) != 3 {
|
|
return nil, errors.New("Malformed token string")
|
|
}
|
|
|
|
var token loginToken
|
|
// decode each of the strParts
|
|
userBytes, err := base64.RawURLEncoding.DecodeString(strParts[0])
|
|
if err != nil {
|
|
return nil, errors.New("Invalid user ID")
|
|
}
|
|
token.UserID = string(userBytes)
|
|
|
|
// first decode the time to a string
|
|
timeBytes, err := base64.RawURLEncoding.DecodeString(strParts[1])
|
|
if err != nil {
|
|
return nil, errors.New("Invalid creation time")
|
|
}
|
|
// now convert the string to an integer
|
|
creationTime, err := strconv.ParseInt(string(timeBytes), 10, 64)
|
|
if err != nil {
|
|
return nil, errors.New("Invalid creation time")
|
|
}
|
|
token.CreationTime = creationTime
|
|
|
|
randomBytes, err := base64.RawURLEncoding.DecodeString(strParts[2])
|
|
if err != nil {
|
|
return nil, errors.New("Invalid random part")
|
|
}
|
|
token.UserID = string(randomBytes)
|
|
|
|
token = loginToken{
|
|
UserID: string(userBytes),
|
|
CreationTime: creationTime,
|
|
RandomPart: string(randomBytes),
|
|
}
|
|
return &token, nil
|
|
}
|
|
|
|
// Checks whether the token exists in the DB and whether the token is assigned to the current transaction ID
|
|
// Does not validate the userID or the creation time expiry
|
|
// Returns nil if successful
|
|
func checkDBToken(tokenStr string, txnID string) error {
|
|
// if the client has provided a transaction id, try to lock the token to that ID
|
|
if txnID != "" {
|
|
if err := LinkToken(tokenStr, txnID); err != nil {
|
|
// TODO: should we abort the login attempt or something else?
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// StoreLoginToken stores the login token in the database
|
|
// Returns nil if successful
|
|
func StoreLoginToken(tokenStr string) error {
|
|
return nil
|
|
}
|
|
|
|
// DeleteLoginToken Deletes a token from the DB
|
|
// used to delete a token that has already been used
|
|
// Returns nil if successful
|
|
func DeleteLoginToken(tokenStr string) error {
|
|
return nil
|
|
}
|
|
|
|
// LinkToken Links a token to a transaction ID so no other client can try to login using that token
|
|
// as specified in https://matrix.org/docs/spec/client_server/r0.6.1#token-based
|
|
func LinkToken(tokenStr string, txnID string) error {
|
|
return nil
|
|
}
|