mirror of
https://github.com/simple-login/app.git
synced 2025-10-18 19:26:19 +08:00
Send event on account unlink (#2379)
* Send event on account unlink * Move unlink to its own module
This commit is contained in:
parent
b0a94acaba
commit
8d8ca3312a
30 changed files with 75 additions and 54 deletions
|
@ -12,7 +12,7 @@ from app.models import (
|
|||
SenderFormatEnum,
|
||||
AliasSuffixEnum,
|
||||
)
|
||||
from app.proton.utils import perform_proton_account_unlink
|
||||
from app.proton.proton_unlink import perform_proton_account_unlink
|
||||
|
||||
|
||||
def setting_to_dict(user: User):
|
||||
|
|
|
@ -12,7 +12,7 @@ from app.dashboard.views.index import get_stats
|
|||
from app.db import Session
|
||||
from app.image_validation import detect_image_format, ImageFormat
|
||||
from app.models import ApiKey, File, PartnerUser, User
|
||||
from app.proton.utils import get_proton_partner
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
from app.session import logout_session
|
||||
from app.utils import random_string
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ from app.proton.proton_callback_handler import (
|
|||
ProtonCallbackHandler,
|
||||
Action,
|
||||
)
|
||||
from app.proton.utils import get_proton_partner
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
from app.utils import sanitize_next_url, sanitize_scheme
|
||||
|
||||
_authorization_base_url = PROTON_BASE_URL + "/oauth/authorize"
|
||||
|
|
|
@ -39,7 +39,7 @@ from app.models import (
|
|||
SenderFormatEnum,
|
||||
UnsubscribeBehaviourEnum,
|
||||
)
|
||||
from app.proton.utils import perform_proton_account_unlink
|
||||
from app.proton.proton_unlink import perform_proton_account_unlink
|
||||
from app.utils import (
|
||||
random_string,
|
||||
CSRFValidationForm,
|
||||
|
|
|
@ -11,7 +11,7 @@ from app.dashboard.base import dashboard_bp
|
|||
from app.extensions import limiter
|
||||
from app.log import LOG
|
||||
from app.models import PartnerUser, SocialAuth
|
||||
from app.proton.utils import get_proton_partner
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
from app.utils import sanitize_next_url
|
||||
|
||||
_SUDO_GAP = 120
|
||||
|
|
|
@ -22,7 +22,7 @@ from app.models import (
|
|||
PartnerUser,
|
||||
PartnerSubscription,
|
||||
)
|
||||
from app.proton.utils import get_proton_partner
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
|
||||
|
||||
@dashboard_bp.route("/pricing", methods=["GET", "POST"])
|
||||
|
|
|
@ -41,7 +41,8 @@ from app.models import (
|
|||
PartnerSubscription,
|
||||
UnsubscribeBehaviourEnum,
|
||||
)
|
||||
from app.proton.utils import get_proton_partner, can_unlink_proton_account
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
from app.proton.proton_unlink import can_unlink_proton_account
|
||||
from app.utils import (
|
||||
random_string,
|
||||
CSRFValidationForm,
|
||||
|
|
|
@ -8,7 +8,7 @@ from app.errors import ProtonPartnerNotSetUp
|
|||
from app.events.generated import event_pb2
|
||||
from app.log import LOG
|
||||
from app.models import User, PartnerUser, SyncEvent
|
||||
from app.proton.utils import get_proton_partner
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
from typing import Optional
|
||||
|
||||
NOTIFICATION_CHANNEL = "simplelogin_sync_events"
|
||||
|
|
|
@ -24,7 +24,7 @@ _sym_db = _symbol_database.Default()
|
|||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0b\x65vent.proto\x12\x12simplelogin_events\":\n\x0fUserPlanChanged\x12\x15\n\rplan_end_time\x18\x01 \x01(\r\x12\x10\n\x08lifetime\x18\x02 \x01(\x08\"\r\n\x0bUserDeleted\"\\\n\x0c\x41liasCreated\x12\n\n\x02id\x18\x01 \x01(\r\x12\r\n\x05\x65mail\x18\x02 \x01(\t\x12\x0c\n\x04note\x18\x03 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x04 \x01(\x08\x12\x12\n\ncreated_at\x18\x05 \x01(\r\"T\n\x12\x41liasStatusChanged\x12\n\n\x02id\x18\x01 \x01(\r\x12\r\n\x05\x65mail\x18\x02 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x03 \x01(\x08\x12\x12\n\ncreated_at\x18\x04 \x01(\r\")\n\x0c\x41liasDeleted\x12\n\n\x02id\x18\x01 \x01(\r\x12\r\n\x05\x65mail\x18\x02 \x01(\t\"D\n\x10\x41liasCreatedList\x12\x30\n\x06\x65vents\x18\x01 \x03(\x0b\x32 .simplelogin_events.AliasCreated\"\x93\x03\n\x0c\x45ventContent\x12?\n\x10user_plan_change\x18\x01 \x01(\x0b\x32#.simplelogin_events.UserPlanChangedH\x00\x12\x37\n\x0cuser_deleted\x18\x02 \x01(\x0b\x32\x1f.simplelogin_events.UserDeletedH\x00\x12\x39\n\ralias_created\x18\x03 \x01(\x0b\x32 .simplelogin_events.AliasCreatedH\x00\x12\x45\n\x13\x61lias_status_change\x18\x04 \x01(\x0b\x32&.simplelogin_events.AliasStatusChangedH\x00\x12\x39\n\ralias_deleted\x18\x05 \x01(\x0b\x32 .simplelogin_events.AliasDeletedH\x00\x12\x41\n\x11\x61lias_create_list\x18\x06 \x01(\x0b\x32$.simplelogin_events.AliasCreatedListH\x00\x42\t\n\x07\x63ontent\"y\n\x05\x45vent\x12\x0f\n\x07user_id\x18\x01 \x01(\r\x12\x18\n\x10\x65xternal_user_id\x18\x02 \x01(\t\x12\x12\n\npartner_id\x18\x03 \x01(\r\x12\x31\n\x07\x63ontent\x18\x04 \x01(\x0b\x32 .simplelogin_events.EventContentb\x06proto3')
|
||||
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0b\x65vent.proto\x12\x12simplelogin_events\":\n\x0fUserPlanChanged\x12\x15\n\rplan_end_time\x18\x01 \x01(\r\x12\x10\n\x08lifetime\x18\x02 \x01(\x08\"\r\n\x0bUserDeleted\"\\\n\x0c\x41liasCreated\x12\n\n\x02id\x18\x01 \x01(\r\x12\r\n\x05\x65mail\x18\x02 \x01(\t\x12\x0c\n\x04note\x18\x03 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x04 \x01(\x08\x12\x12\n\ncreated_at\x18\x05 \x01(\r\"T\n\x12\x41liasStatusChanged\x12\n\n\x02id\x18\x01 \x01(\r\x12\r\n\x05\x65mail\x18\x02 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x03 \x01(\x08\x12\x12\n\ncreated_at\x18\x04 \x01(\r\")\n\x0c\x41liasDeleted\x12\n\n\x02id\x18\x01 \x01(\r\x12\r\n\x05\x65mail\x18\x02 \x01(\t\"D\n\x10\x41liasCreatedList\x12\x30\n\x06\x65vents\x18\x01 \x03(\x0b\x32 .simplelogin_events.AliasCreated\"\x0e\n\x0cUserUnlinked\"\xce\x03\n\x0c\x45ventContent\x12?\n\x10user_plan_change\x18\x01 \x01(\x0b\x32#.simplelogin_events.UserPlanChangedH\x00\x12\x37\n\x0cuser_deleted\x18\x02 \x01(\x0b\x32\x1f.simplelogin_events.UserDeletedH\x00\x12\x39\n\ralias_created\x18\x03 \x01(\x0b\x32 .simplelogin_events.AliasCreatedH\x00\x12\x45\n\x13\x61lias_status_change\x18\x04 \x01(\x0b\x32&.simplelogin_events.AliasStatusChangedH\x00\x12\x39\n\ralias_deleted\x18\x05 \x01(\x0b\x32 .simplelogin_events.AliasDeletedH\x00\x12\x41\n\x11\x61lias_create_list\x18\x06 \x01(\x0b\x32$.simplelogin_events.AliasCreatedListH\x00\x12\x39\n\ruser_unlinked\x18\x07 \x01(\x0b\x32 .simplelogin_events.UserUnlinkedH\x00\x42\t\n\x07\x63ontent\"y\n\x05\x45vent\x12\x0f\n\x07user_id\x18\x01 \x01(\r\x12\x18\n\x10\x65xternal_user_id\x18\x02 \x01(\t\x12\x12\n\npartner_id\x18\x03 \x01(\r\x12\x31\n\x07\x63ontent\x18\x04 \x01(\x0b\x32 .simplelogin_events.EventContentb\x06proto3')
|
||||
|
||||
_globals = globals()
|
||||
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
||||
|
@ -43,8 +43,10 @@ if not _descriptor._USE_C_DESCRIPTORS:
|
|||
_globals['_ALIASDELETED']._serialized_end=331
|
||||
_globals['_ALIASCREATEDLIST']._serialized_start=333
|
||||
_globals['_ALIASCREATEDLIST']._serialized_end=401
|
||||
_globals['_EVENTCONTENT']._serialized_start=404
|
||||
_globals['_EVENTCONTENT']._serialized_end=807
|
||||
_globals['_EVENT']._serialized_start=809
|
||||
_globals['_EVENT']._serialized_end=930
|
||||
_globals['_USERUNLINKED']._serialized_start=403
|
||||
_globals['_USERUNLINKED']._serialized_end=417
|
||||
_globals['_EVENTCONTENT']._serialized_start=420
|
||||
_globals['_EVENTCONTENT']._serialized_end=882
|
||||
_globals['_EVENT']._serialized_start=884
|
||||
_globals['_EVENT']._serialized_end=1005
|
||||
# @@protoc_insertion_point(module_scope)
|
||||
|
|
|
@ -57,21 +57,27 @@ class AliasCreatedList(_message.Message):
|
|||
events: _containers.RepeatedCompositeFieldContainer[AliasCreated]
|
||||
def __init__(self, events: _Optional[_Iterable[_Union[AliasCreated, _Mapping]]] = ...) -> None: ...
|
||||
|
||||
class UserUnlinked(_message.Message):
|
||||
__slots__ = ()
|
||||
def __init__(self) -> None: ...
|
||||
|
||||
class EventContent(_message.Message):
|
||||
__slots__ = ("user_plan_change", "user_deleted", "alias_created", "alias_status_change", "alias_deleted", "alias_create_list")
|
||||
__slots__ = ("user_plan_change", "user_deleted", "alias_created", "alias_status_change", "alias_deleted", "alias_create_list", "user_unlinked")
|
||||
USER_PLAN_CHANGE_FIELD_NUMBER: _ClassVar[int]
|
||||
USER_DELETED_FIELD_NUMBER: _ClassVar[int]
|
||||
ALIAS_CREATED_FIELD_NUMBER: _ClassVar[int]
|
||||
ALIAS_STATUS_CHANGE_FIELD_NUMBER: _ClassVar[int]
|
||||
ALIAS_DELETED_FIELD_NUMBER: _ClassVar[int]
|
||||
ALIAS_CREATE_LIST_FIELD_NUMBER: _ClassVar[int]
|
||||
USER_UNLINKED_FIELD_NUMBER: _ClassVar[int]
|
||||
user_plan_change: UserPlanChanged
|
||||
user_deleted: UserDeleted
|
||||
alias_created: AliasCreated
|
||||
alias_status_change: AliasStatusChanged
|
||||
alias_deleted: AliasDeleted
|
||||
alias_create_list: AliasCreatedList
|
||||
def __init__(self, user_plan_change: _Optional[_Union[UserPlanChanged, _Mapping]] = ..., user_deleted: _Optional[_Union[UserDeleted, _Mapping]] = ..., alias_created: _Optional[_Union[AliasCreated, _Mapping]] = ..., alias_status_change: _Optional[_Union[AliasStatusChanged, _Mapping]] = ..., alias_deleted: _Optional[_Union[AliasDeleted, _Mapping]] = ..., alias_create_list: _Optional[_Union[AliasCreatedList, _Mapping]] = ...) -> None: ...
|
||||
user_unlinked: UserUnlinked
|
||||
def __init__(self, user_plan_change: _Optional[_Union[UserPlanChanged, _Mapping]] = ..., user_deleted: _Optional[_Union[UserDeleted, _Mapping]] = ..., alias_created: _Optional[_Union[AliasCreated, _Mapping]] = ..., alias_status_change: _Optional[_Union[AliasStatusChanged, _Mapping]] = ..., alias_deleted: _Optional[_Union[AliasDeleted, _Mapping]] = ..., alias_create_list: _Optional[_Union[AliasCreatedList, _Mapping]] = ..., user_unlinked: _Optional[_Union[UserUnlinked, _Mapping]] = ...) -> None: ...
|
||||
|
||||
class Event(_message.Message):
|
||||
__slots__ = ("user_id", "external_user_id", "partner_id", "content")
|
||||
|
|
|
@ -37,7 +37,7 @@ from app.models import (
|
|||
PartnerSubscription,
|
||||
)
|
||||
from app.pgp_utils import load_public_key
|
||||
from app.proton.utils import get_proton_partner
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
|
||||
|
||||
def fake_data():
|
||||
|
|
|
@ -14,7 +14,7 @@ from app.models import (
|
|||
Job,
|
||||
PartnerUser,
|
||||
)
|
||||
from app.proton.utils import get_proton_partner
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
from events.event_sink import EventSink
|
||||
|
||||
|
||||
|
|
23
app/proton/proton_partner.py
Normal file
23
app/proton/proton_partner.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from typing import Optional
|
||||
|
||||
from app.db import Session
|
||||
from app.errors import ProtonPartnerNotSetUp
|
||||
from app.models import Partner
|
||||
|
||||
PROTON_PARTNER_NAME = "Proton"
|
||||
_PROTON_PARTNER: Optional[Partner] = None
|
||||
|
||||
|
||||
def get_proton_partner() -> Partner:
|
||||
global _PROTON_PARTNER
|
||||
if _PROTON_PARTNER is None:
|
||||
partner = Partner.get_by(name=PROTON_PARTNER_NAME)
|
||||
if partner is None:
|
||||
raise ProtonPartnerNotSetUp
|
||||
Session.expunge(partner)
|
||||
_PROTON_PARTNER = partner
|
||||
return _PROTON_PARTNER
|
||||
|
||||
|
||||
def is_proton_partner(partner: Partner) -> bool:
|
||||
return partner.name == PROTON_PARTNER_NAME
|
|
@ -1,31 +1,13 @@
|
|||
from typing import Optional
|
||||
|
||||
from newrelic import agent
|
||||
|
||||
from app.db import Session
|
||||
from app.errors import ProtonPartnerNotSetUp
|
||||
from app.events.event_dispatcher import EventDispatcher
|
||||
from app.events.generated.event_pb2 import EventContent, UserUnlinked
|
||||
from app.log import LOG
|
||||
from app.models import Partner, PartnerUser, User
|
||||
from app.models import User, PartnerUser
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
from app.user_audit_log_utils import emit_user_audit_log, UserAuditLogAction
|
||||
|
||||
PROTON_PARTNER_NAME = "Proton"
|
||||
_PROTON_PARTNER: Optional[Partner] = None
|
||||
|
||||
|
||||
def get_proton_partner() -> Partner:
|
||||
global _PROTON_PARTNER
|
||||
if _PROTON_PARTNER is None:
|
||||
partner = Partner.get_by(name=PROTON_PARTNER_NAME)
|
||||
if partner is None:
|
||||
raise ProtonPartnerNotSetUp
|
||||
Session.expunge(partner)
|
||||
_PROTON_PARTNER = partner
|
||||
return _PROTON_PARTNER
|
||||
|
||||
|
||||
def is_proton_partner(partner: Partner) -> bool:
|
||||
return partner.name == PROTON_PARTNER_NAME
|
||||
|
||||
|
||||
def can_unlink_proton_account(user: User) -> bool:
|
||||
return (user.flags & User.FLAG_CREATED_FROM_PARTNER) == 0
|
||||
|
@ -45,6 +27,9 @@ def perform_proton_account_unlink(current_user: User) -> bool:
|
|||
action=UserAuditLogAction.UnlinkAccount,
|
||||
message=f"User has unlinked the account (email={partner_user.partner_email} | external_user_id={partner_user.external_user_id})",
|
||||
)
|
||||
EventDispatcher.send_event(
|
||||
partner_user.user, EventContent(user_unlinked=UserUnlinked())
|
||||
)
|
||||
PartnerUser.delete(partner_user.id)
|
||||
Session.commit()
|
||||
agent.record_custom_event("AccountUnlinked", {"partner": proton_partner.name})
|
2
cron.py
2
cron.py
|
@ -59,7 +59,7 @@ from app.models import (
|
|||
ApiToCookieToken,
|
||||
)
|
||||
from app.pgp_utils import load_public_key_and_check, PGPException
|
||||
from app.proton.utils import get_proton_partner
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
from app.user_audit_log_utils import emit_user_audit_log, UserAuditLogAction
|
||||
from app.utils import sanitize_email
|
||||
from server import create_light_app
|
||||
|
|
|
@ -6,7 +6,7 @@ from app.db import Session
|
|||
from app.log import LOG
|
||||
from app.models import Mailbox, Contact, SLDomain, Partner
|
||||
from app.pgp_utils import load_public_key
|
||||
from app.proton.utils import PROTON_PARTNER_NAME
|
||||
from app.proton.proton_partner import PROTON_PARTNER_NAME
|
||||
from server import create_light_app
|
||||
|
||||
|
||||
|
|
|
@ -34,6 +34,9 @@ message AliasCreatedList {
|
|||
repeated AliasCreated events = 1;
|
||||
}
|
||||
|
||||
message UserUnlinked {
|
||||
}
|
||||
|
||||
message EventContent {
|
||||
oneof content {
|
||||
UserPlanChanged user_plan_change = 1;
|
||||
|
@ -42,6 +45,7 @@ message EventContent {
|
|||
AliasStatusChanged alias_status_change = 4;
|
||||
AliasDeleted alias_deleted = 5;
|
||||
AliasCreatedList alias_create_list = 6;
|
||||
UserUnlinked user_unlinked = 7;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ from flask import url_for
|
|||
from app import config
|
||||
from app.db import Session
|
||||
from app.models import User, PartnerUser
|
||||
from app.proton.utils import get_proton_partner
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
from tests.api.utils import get_new_user_and_api_key
|
||||
from tests.utils import login, random_token, random_email
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ from app.models import (
|
|||
PartnerSubscription,
|
||||
User,
|
||||
)
|
||||
from app.proton.utils import get_proton_partner
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
from tests.utils import create_new_user, random_token
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from app.events.event_dispatcher import Dispatcher
|
||||
from app.events.generated import event_pb2
|
||||
from app.models import PartnerUser, User
|
||||
from app.proton.utils import get_proton_partner
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
from tests.utils import create_new_user, random_token
|
||||
from typing import Tuple
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ from app import config
|
|||
from app.events.generated.event_pb2 import EventContent, AliasDeleted
|
||||
from app.jobs.send_event_job import SendEventToWebhookJob
|
||||
from app.models import PartnerUser
|
||||
from app.proton.utils import get_proton_partner
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
from events.event_sink import ConsoleEventSink
|
||||
from tests.utils import create_new_user, random_token
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ import arrow
|
|||
from app import config
|
||||
from app.db import Session
|
||||
from app.models import User, Job, PartnerSubscription, PartnerUser, ManualSubscription
|
||||
from app.proton.utils import get_proton_partner
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
from tests.utils import random_email, random_token
|
||||
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ from app.account_linking import (
|
|||
)
|
||||
from app.db import Session
|
||||
from app.models import User, PartnerUser, PartnerSubscription
|
||||
from app.proton.utils import get_proton_partner
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
from app.utils import random_string
|
||||
from tests.utils import random_email
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ from app.proton.proton_callback_handler import (
|
|||
generate_account_not_allowed_to_log_in,
|
||||
)
|
||||
from app.models import User, PartnerUser, Job, JobState
|
||||
from app.proton.utils import get_proton_partner
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
from app.utils import random_string
|
||||
from typing import Optional
|
||||
from tests.utils import random_email
|
||||
|
|
|
@ -19,7 +19,7 @@ from app.account_linking import (
|
|||
from app.db import Session
|
||||
from app.errors import AccountAlreadyLinkedToAnotherPartnerException
|
||||
from app.models import Partner, PartnerUser, User, UserAuditLog
|
||||
from app.proton.utils import get_proton_partner
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
from app.user_audit_log_utils import UserAuditLogAction
|
||||
from app.utils import random_string, canonicalize_email
|
||||
from tests.utils import random_email
|
||||
|
|
|
@ -3,7 +3,7 @@ import re
|
|||
from app.alias_suffix import get_alias_suffixes
|
||||
from app.db import Session
|
||||
from app.models import SLDomain, PartnerUser, AliasOptions, CustomDomain
|
||||
from app.proton.utils import get_proton_partner
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
from init_app import add_sl_domains
|
||||
from tests.utils import create_new_user, random_token
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ from app.models import (
|
|||
PartnerSubscription,
|
||||
PartnerUser,
|
||||
)
|
||||
from app.proton.utils import get_proton_partner
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
from tests.utils import create_new_user, random_string, random_email
|
||||
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ from app.custom_domain_validation import CustomDomainValidation
|
|||
from app.db import Session
|
||||
from app.dns_utils import InMemoryDNSClient
|
||||
from app.models import CustomDomain, User
|
||||
from app.proton.utils import get_proton_partner
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
from app.utils import random_string
|
||||
from tests.utils import create_new_user, random_domain
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from app.db import Session
|
||||
from app.models import SLDomain, PartnerUser, AliasOptions
|
||||
from app.proton.utils import get_proton_partner
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
from init_app import add_sl_domains
|
||||
from tests.utils import create_new_user, random_token
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import jinja2
|
|||
from flask import url_for
|
||||
|
||||
from app.models import User, PartnerUser
|
||||
from app.proton.utils import get_proton_partner
|
||||
from app.proton.proton_partner import get_proton_partner
|
||||
from app.utils import random_string
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue