Beispiel #1
0
def login_user():
    email = request.json['email']
    password = request.json['password']

    user = User.query.filter_by(email=email).first_or_404()

    if not user.verify_password(password):
        return jsonify({"error": "Incorrect password"}), 403

    payload = {"id": user.id, "exp": datetime.utcnow() + timedelta(hours=12)}

    token = jwt.encode(self=None,
                       payload=payload,
                       key=current_app.config['AUTH_SECRET_KEY'])

    return jsonify({"token": token}), 200
Beispiel #2
0
    def generate(user, kind):
        """
        Generate a session token for the user, providing the level and exp.

        :param user: The user to generate the token for.
        :type user: models.User
        :param kind: The type of token to generate
        :type kind: models.TokenType
        :return: JWT encoded claims
        :rtype: str
        :Example:

        >>> user = User.objects.create(...)
        >>> token = TokenManager.generate(user, TokenType.LEVEL_ZERO)
        """
        assert(isinstance(kind, TokenType))
        assert(kind in list(TokenType))

        now = timezone.now()

        claims = {
            'iat': now,
            'exp': now + kind.ttl(),
            'sub': user.uuid,
            'jti': user.nonce(),
            'typ': int(kind.value),
        }

        jwt = PyJWT(options={
            'require_exp': True,
            'require_iat': True,
        })

        ValidTokens.objects.create(
            jti=claims['jti'], exp=claims['exp'], user=user)

        log.info("Generated a {} for {}".format(kind, user.uuid))
        return jwt.encode(claims, user.private_key(), algorithm='HS256')
Beispiel #3
0
class Jwt(object):
    """ Base class implementing JWT support. """
    # For easier access
    Error = exc.JwtError

    AuthHeaderMissingError = exc.AuthHeaderMissingError
    ClaimMissing = exc.ClaimMissing
    BadAuthHeaderError = exc.BadAuthHeaderError
    InvalidTokenError = exc.InvalidTokenError
    NotAuthorizedError = exc.NotAuthorizedError
    UserNotFoundError = exc.UserNotFoundError
    TokenExpired = exc.TokenExpired

    def __init__(self):
        self.pyjwt = PyJWT()
        self.header_prefix = 'JWT'
        self.token_ttl = timedelta(seconds=300)
        self.not_before = timedelta(seconds=0)
        self.algorithm = 'HS256'
        self.verify_claims = ['signature', 'exp', 'iat', 'nbf']
        self.require_claims = ['exp', 'iat', 'nbf']
        self.leeway = 0
        self.secret_key = None

    def authorize(self, auth_header: Optional[str]) -> User:
        """ Given an Authorization Header try to get the matching user.

        Args:
            auth_header (Optional[str]):
                The full content of the 'Authorization' header as read from the
                request. The way it's stored in the request will depend on
                framework used.

        Returns:
            User: The user instance represented by the token read from *auth_header*.

        Raises:
            Jwt.AuthHeaderMissingError:
                If the given auth header is empty or `None`.
            Jwt.BadAuthHeaderError:
                If the given auth header cannot be parsed. This is either if
                the Authorization header is completely wrong or the header
                prefix does not match whatever is set in `Jwt.header_prefix`
            Jwt.InvalidTokenError:
                Cannot decode the JWT token.
            Jwt.UserNotFoundError:
                User represented by the token was not found. This might happen
                if the user is deleted after the token is issued but before it
                expires.
        """
        if not auth_header:
            raise self.AuthHeaderMissingError()

        token = self.get_token_from_header(auth_header)
        return self.authorize_token(token)

    def authorize_token(self, token: str) -> User:
        """ Get user for a given token.

        This method can be useful if the token is not coming from an HTTP header but
        other means (like websockets).

        Args:
            token:
                JWT token representing a user. This actually only stores the user
                ID, the actual user is retrievieved with `user_from_payload()` method.

        Returns:
            A user corresponding to the given token (if it's valid).

        Raises:
            Jwt.InvalidTokenError:
                If the token can't be decoded.
            Jwt.UserNotFoundError:
                If the user ID stored in the token cannot be found
                (.user_from_payload() call returned ``None``).
        """
        try:
            payload = self.decode_token(token)
        except PyJwtInvalidTokenError:
            raise self.InvalidTokenError(f"Failed to decode token '{token}'")

        user = self.user_from_payload(payload)
        if user is None:
            raise self.UserNotFoundError()

        return user

    def get_token_from_header(self, auth_header: str) -> str:
        """ Parse auth header and extract the token

        Args:
            auth_header:
                The content of the auth header as received with in the request.

        Returns:
            The JWT token stored in the header
        """
        # Verify the token is in the right format
        parts = auth_header.split()
        if parts[0] != self.header_prefix:
            raise self.BadAuthHeaderError(
                f"Bad auth header: '{parts[0]}', expected '{self.header_prefix}'"
            )
        elif len(parts) == 1:
            raise self.InvalidTokenError("Missing or empty token")

        return parts[1]

    def user_payload(self, user) -> JsonDict:
        """ Return payload for the given user.

        This method must be implemented by the subclasses in order to integrate
        with any storage used by the project (jwtlib itself is framework
        agnostic).
        """
        raise NotImplementedError("user_payload() method must be implemented")

    def user_from_payload(self, payload: JsonDict) -> User:
        """ Return a user for the given JWT payload.

        This method must be implemented by the subclasses in order to integrate
        with any storage used by the project (jwtlib itself is framework
        agnostic).

        This method is the opposite of `user_payload`.
        """
        raise NotImplementedError(
            "user_from_payload() method must be implemented")

    def generate_token(self, user: Optional[User]) -> str:
        """ Generate JWT token for the given user. """
        if user is None:
            raise self.NotAuthorizedError("No user to generate token for")

        headers = self.create_headers()
        payload = self.create_payload()
        payload.update(self.user_payload(user))

        missing = frozenset(self.require_claims) - frozenset(payload.keys())
        if missing:
            raise self.ClaimMissing("JWT payload is missing claims: {}".format(
                ', '.join(missing)))

        return self.pyjwt.encode(payload,
                                 self.secret_key,
                                 algorithm=self.algorithm,
                                 headers=headers)

    def create_headers(self) -> Optional[JsonDict]:
        """ Create general JWT token headers.

        This method can be overloaded in subclasses to customize the way tokens
        are generated.
        """
        return None

    def create_payload(self) -> JsonDict:
        """ Create core JWT payload.

        This will contain the fields that are required by JWT like expiration
        and will be included in every token generated.

        Be careful when you overload this method in subclasses as you will have
        to take care of including the necessary fields or the jwtlib will break.
        """
        iat = datetime.utcnow()

        return {
            'iat': iat,
            'exp': iat + self.token_ttl,
            'nbf': iat + self.not_before,
        }

    def decode_token(self, token: str) -> JsonDict:
        """ Decode the token and return it's payload. """
        opts = {'require_' + claim: True for claim in self.require_claims}
        opts.update({'verify_' + claim: True for claim in self.verify_claims})

        try:
            return self.pyjwt.decode(token,
                                     key=self.secret_key,
                                     options=opts,
                                     algorithms=[self.algorithm],
                                     leeway=self.leeway)
        except ExpiredSignatureError:
            raise self.TokenExpired()
Beispiel #4
0
class JWT:
    __slots__ = ('__private_key', '__public_key', '__jwt', '__expires',
                 '__nbf_delta', '__algorithm')

    DEFAULT_EXPIRATION = 86400 * 30  # one month
    NBF_DELTA = 20
    ALGORITHMS = tuple({
        'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES521', 'ES512', 'PS256',
        'PS384', 'PS512'
    })

    def __init__(self,
                 private_key: RSAPrivateKey = None,
                 public_key: RSAPublicKey = None,
                 expires=None,
                 nbf_delta=None,
                 algorithm="RS512"):

        self.__private_key = private_key
        self.__public_key = public_key
        self.__jwt = PyJWT(algorithms=self.ALGORITHMS)
        self.__expires = expires or self.DEFAULT_EXPIRATION
        self.__nbf_delta = nbf_delta or self.NBF_DELTA
        self.__algorithm = algorithm

    def _date_to_timestamp(self, value, default, timedelta_func=add):
        if isinstance(value, timedelta):
            return timedelta_func(time.time(), value.total_seconds())
        elif isinstance(value, datetime):
            return value.timestamp()
        elif isinstance(value, (int, float)):
            return value
        elif value is Ellipsis:
            return default()

        raise ValueError(type(value))

    def encode(self,
               expired: DateType = ...,
               nbf: DateType = ...,
               **claims) -> str:
        if not self.__private_key:
            raise RuntimeError("Can't encode without private key")

        claims.update(
            dict(
                exp=int(
                    self._date_to_timestamp(
                        expired, lambda: time.time() + self.__expires)),
                nbf=int(
                    self._date_to_timestamp(
                        nbf,
                        lambda: time.time() - self.__nbf_delta,
                        timedelta_func=sub)),
            ))

        return self.__jwt.encode(
            claims,
            self.__private_key,
            algorithm=self.__algorithm,
        ).decode()

    def decode(self, token: str, verify=True, **kwargs) -> dict:
        if not self.__public_key:
            raise RuntimeError("Can't decode without public key")

        return self.__jwt.decode(token,
                                 key=self.__public_key,
                                 verify=verify,
                                 algorithms=self.ALGORITHMS,
                                 **kwargs)
Beispiel #5
0
class JWT:
    __slots__ = (
        "__private_key", "__public_key", "__jwt",
        "__expires", "__nbf_delta", "__algorithm",
    )

    DEFAULT_EXPIRATION = 86400 * 30  # one month
    NBF_DELTA = 20
    ALGORITHMS = tuple({
        "RS256", "RS384", "RS512", "ES256", "ES384",
        "ES521", "ES512", "PS256", "PS384", "PS512",
    })

    def __init__(
        self,
        private_key: Optional[RSAPrivateKey] = None,
        public_key: Optional[RSAPublicKey] = None,
        expires: Optional[int] = None,
        nbf_delta: Optional[int] = None,
        algorithm: str = "RS512",
    ):

        self.__private_key = private_key
        self.__public_key = public_key
        self.__jwt = PyJWT(algorithms=self.ALGORITHMS)
        self.__expires = expires or self.DEFAULT_EXPIRATION
        self.__nbf_delta = nbf_delta or self.NBF_DELTA
        self.__algorithm = algorithm

    def _date_to_timestamp(
        self,
        value: DateType,
        default: Callable[[], R],
        timedelta_func: Callable[[float, float], int] = add,
    ) -> Union[int, float, R]:
        if isinstance(value, timedelta):
            return timedelta_func(time.time(), value.total_seconds())
        elif isinstance(value, datetime):
            return value.timestamp()
        elif isinstance(value, (int, float)):
            return value
        elif value is Ellipsis:
            return default()

        raise ValueError(type(value))

    def encode(
        self,
        expired: DateType = ...,
        nbf: DateType = ...,
        **claims: int
    ) -> str:
        if not self.__private_key:
            raise RuntimeError("Can't encode without private key")

        claims.update(
            dict(
                exp=int(
                    self._date_to_timestamp(
                        expired,
                        lambda: time.time() + self.__expires,
                    ),
                ),
                nbf=int(
                    self._date_to_timestamp(
                        nbf,
                        lambda: time.time() - self.__nbf_delta,
                        timedelta_func=sub,
                    ),
                ),
            ),
        )

        return self.__jwt.encode(
            claims,
            self.__private_key,
            algorithm=self.__algorithm,
        ).decode()

    def decode(
        self, token: str, verify: bool = True, **kwargs: Any
    ) -> Dict[str, Any]:
        if not self.__public_key:
            raise RuntimeError("Can't decode without public key")

        return self.__jwt.decode(
            token,
            key=self.__public_key,
            verify=verify,
            algorithms=self.ALGORITHMS,
            **kwargs,
        )