-
-
Notifications
You must be signed in to change notification settings - Fork 37.8k
Long-lived access token #16453
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Long-lived access token #16453
Changes from 7 commits
0d85e29
312cda5
fa01a29
ad0189c
19256ac
463b6ed
eeb4ef7
1e52bdb
80b26ef
26126fb
4dd9f59
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,11 +2,13 @@ | |
| import asyncio | ||
| import logging | ||
| from collections import OrderedDict | ||
| from datetime import timedelta | ||
| from typing import Any, Dict, List, Optional, Tuple, cast | ||
|
|
||
| import jwt | ||
|
|
||
| from homeassistant import data_entry_flow | ||
| from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION | ||
| from homeassistant.core import callback, HomeAssistant | ||
| from homeassistant.util import dt as dt_util | ||
|
|
||
|
|
@@ -242,8 +244,12 @@ async def async_get_enabled_mfa(self, user: models.User) -> Dict[str, str]: | |
| modules[module_id] = module.name | ||
| return modules | ||
|
|
||
| async def async_create_refresh_token(self, user: models.User, | ||
| client_id: Optional[str] = None) \ | ||
| async def async_create_refresh_token( | ||
| self, user: models.User, client_id: Optional[str] = None, | ||
| client_name: Optional[str] = None, | ||
| client_icon: Optional[str] = None, | ||
| token_type: Optional[str] = None, | ||
| access_token_expiration: timedelta = ACCESS_TOKEN_EXPIRATION) \ | ||
| -> models.RefreshToken: | ||
| """Create a new refresh token for a user.""" | ||
| if not user.is_active: | ||
|
|
@@ -254,10 +260,47 @@ async def async_create_refresh_token(self, user: models.User, | |
| 'System generated users cannot have refresh tokens connected ' | ||
| 'to a client.') | ||
|
|
||
| if not user.system_generated and client_id is None: | ||
| if token_type is None: | ||
| if user.system_generated: | ||
| token_type = models.TOKEN_TYPE_SYSTEM | ||
| else: | ||
| token_type = models.TOKEN_TYPE_NORMAL | ||
|
|
||
| if user.system_generated and token_type != models.TOKEN_TYPE_SYSTEM: | ||
| raise ValueError( | ||
| 'System generated users can only have system type ' | ||
| 'refresh tokens') | ||
|
|
||
| if not user.system_generated and \ | ||
| token_type == models.TOKEN_TYPE_SYSTEM: | ||
| raise ValueError( | ||
| 'Only system generated users can have system type ' | ||
| 'refresh tokens') | ||
|
|
||
| if token_type == models.TOKEN_TYPE_NORMAL and client_id is None: | ||
| raise ValueError('Client is required to generate a refresh token.') | ||
|
|
||
| return await self._store.async_create_refresh_token(user, client_id) | ||
| if (token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN and | ||
|
awarecan marked this conversation as resolved.
|
||
| client_name is None): | ||
| raise ValueError('Client_name is required for long-lived access ' | ||
| 'token') | ||
|
|
||
| if token_type == models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that this is a weird check. We shouldn't enforce this but instead leave this up to the user.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Current is Did you want to change to |
||
| for token in list(user.refresh_tokens.values()): | ||
| if (token.client_name == client_name and token.token_type == | ||
|
awarecan marked this conversation as resolved.
|
||
| models.TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN): | ||
| # Each client_name can only have one | ||
| # long_lived_access_token type of refresh token, remove | ||
| # exist refresh token, a new one will be created. | ||
| # As a side effect, all previous issued long-lived access | ||
| # token for this client_id will be invoked. | ||
| # Continue scan all tokens in case there are multiple | ||
| # long_lived_access_token type of refresh tokens. | ||
| await self._store.async_remove_refresh_token(token) | ||
|
|
||
| return await self._store.async_create_refresh_token( | ||
| user, client_id, client_name, client_icon, | ||
| token_type, access_token_expiration) | ||
|
|
||
| async def async_get_refresh_token( | ||
| self, token_id: str) -> Optional[models.RefreshToken]: | ||
|
|
@@ -280,10 +323,11 @@ def async_create_access_token(self, | |
| refresh_token: models.RefreshToken) -> str: | ||
| """Create a new access token.""" | ||
| # pylint: disable=no-self-use | ||
| now = dt_util.utcnow() | ||
| return jwt.encode({ | ||
| 'iss': refresh_token.id, | ||
| 'iat': dt_util.utcnow(), | ||
| 'exp': dt_util.utcnow() + refresh_token.access_token_expiration, | ||
| 'iat': now, | ||
| 'exp': now + refresh_token.access_token_expiration, | ||
| }, refresh_token.jwt_key, algorithm='HS256').decode() | ||
|
|
||
| async def async_validate_access_token( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can collapse this if-statement in the previous one: