



  

<!DOCTYPE html>
<html
  lang="en"
  
  data-color-mode="auto" data-light-theme="light" data-dark-theme="dark"
  data-a11y-animated-images="system" data-a11y-link-underlines="true"
  
  >




  <head>
    <meta charset="utf-8">
  <link rel="dns-prefetch" href="https://github.githubassets.com">
  <link rel="dns-prefetch" href="https://avatars.githubusercontent.com">
  <link rel="dns-prefetch" href="https://github-cloud.s3.amazonaws.com">
  <link rel="dns-prefetch" href="https://user-images.githubusercontent.com/">
  <link rel="preconnect" href="https://github.githubassets.com" crossorigin>
  <link rel="preconnect" href="https://avatars.githubusercontent.com">

<script type="importmap">{"imports":{"react":"https://github.githubassets.com/assets/react-e27d1b3e03961e68.js","react-dom":"https://github.githubassets.com/assets/react-dom-e5fd46a22d5c4058.js","react-dom/client":"https://github.githubassets.com/assets/react-dom-client-1b4a3ee065998cea.js","react-is":"https://github.githubassets.com/assets/react-is-e0b593954b4706d8.js","react-reconciler":"https://github.githubassets.com/assets/react-reconciler-8e99e505c4429605.js","react/compiler-runtime":"https://github.githubassets.com/assets/react-compiler-runtime-4610bd6d3de9c049.js","react/jsx-dev-runtime":"https://github.githubassets.com/assets/react-jsx-dev-runtime-ea55d68667d559e5.js","react/jsx-runtime":"https://github.githubassets.com/assets/react-jsx-runtime-4915cb0f5b3aff04.js","scheduler":"https://github.githubassets.com/assets/scheduler-58b860b049ca307c.js"}}</script>
<meta name="react-profiling" content="0" data-turbo-transient="true" />
<meta name="react-import-map" content="react,react-dom,react-dom/client,react-dom/profiling,react-is,react-reconciler,react/compiler-runtime,react/jsx-dev-runtime,react/jsx-runtime,scheduler@777f63f87cd0" data-turbo-track="reload" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-e27d1b3e03961e68.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-compiler-runtime-4610bd6d3de9c049.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/scheduler-58b860b049ca307c.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-dom-e5fd46a22d5c4058.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-dom-client-1b4a3ee065998cea.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-is-e0b593954b4706d8.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-jsx-runtime-4915cb0f5b3aff04.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-reconciler-8e99e505c4429605.js" />

  


  <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/light-5c4e9fc574bf49f3.css" /><link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/light_high_contrast-fb37c309e0603a69.css" /><link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/dark-afff6c53aef9b9d1.css" /><link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/dark_high_contrast-c409873d7987dffa.css" /><link data-color-theme="light" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light-5c4e9fc574bf49f3.css" /><link data-color-theme="light_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_high_contrast-fb37c309e0603a69.css" /><link data-color-theme="light_colorblind" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_colorblind-0f910d8806d5761b.css" /><link data-color-theme="light_colorblind_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_colorblind_high_contrast-a7abd4d4c49ac593.css" /><link data-color-theme="light_tritanopia" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_tritanopia-5fc4c4a9cb41e596.css" /><link data-color-theme="light_tritanopia_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/light_tritanopia_high_contrast-ed0cee9214dedabd.css" /><link data-color-theme="dark" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark-afff6c53aef9b9d1.css" /><link data-color-theme="dark_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_high_contrast-c409873d7987dffa.css" /><link data-color-theme="dark_colorblind" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_colorblind-78a2491d538dab5a.css" /><link data-color-theme="dark_colorblind_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_colorblind_high_contrast-c26b63d7c532d0a2.css" /><link data-color-theme="dark_tritanopia" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_tritanopia-a6f60cea68c08405.css" /><link data-color-theme="dark_tritanopia_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_tritanopia_high_contrast-f66faf28b2ea18b6.css" /><link data-color-theme="dark_dimmed" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_dimmed-6701f6218c53cf5b.css" /><link data-color-theme="dark_dimmed_high_contrast" crossorigin="anonymous" media="all" rel="stylesheet" data-href="https://github.githubassets.com/assets/dark_dimmed_high_contrast-7037fa46ae124d97.css" />

  <style type="text/css">
    :root {
      --tab-size-preference: 4;
    }

    pre, code {
      tab-size: var(--tab-size-preference);
    }
  </style>

    <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-primitives-97df7784617ce1ea.css" />
    <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-9be9fe6313f476af.css" />
    <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/global-62747e27e61258dc.css" />
    <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/github-b04ca6fccda778e2.css" />
  <link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/repository-72bd7d974b5ac1cc.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/code-4c5c0895f723f870.css" />

  

  <script type="application/json" id="client-env">{"locale":"en","featureFlags":["actions_caches_react_shell","actions_enable_background_steps","actions_new_hosted_runner_image_select_sizes_and_versions","actions_runners_react_shell","agent_author_search_expansion","agent_author_search_expansion_ui_pulls","alternate_user_config_repo","async_conversion_coverage_enabled","billing_billable_licenses_cost_center_bucket_fix","billing_budget_expiration","billing_cost_center_list_assigned_resources","billing_discount_threshold_notification","code_quality_enablement_banner_targeting","code_quality_remove_preview","code_view_raf_sticky_lines","codespaces_prebuild_region_target_update","coding_agent_third_party_model_ui","copilot_agent_snippy","copilot_ahp_tool_call_timing","copilot_api_agentic_issue_marshal_yaml","copilot_automation_repo_mcp_servers","copilot_automations_pagination","copilot_base_model_policy_row","copilot_chat_auto_mode_v2","copilot_chat_clear_model_selection_for_default_change","copilot_chat_early_task_provisioning","copilot_chat_persist_session_drafts","copilot_chat_vision_dotcom_chat_ga_gate","copilot_css_textarea_autosize","copilot_custom_copilots","copilot_custom_copilots_feature_preview","copilot_duplicate_thread","copilot_extensions_removal_on_marketplace","copilot_fix_failed_workflows_all_skus","copilot_hide_hovercard","copilot_immersive_task_hyperlinking","copilot_mc_cli_resume_any_users_task","copilot_mission_control_agent_merge_fix_ci","copilot_mission_control_agent_merge_resolve_conflicts","copilot_mission_control_awps_batching","copilot_mission_control_early_stop","copilot_mission_control_managed_sandbox_environments","copilot_mission_control_needs_attention","copilot_mission_control_reasoning_effort","copilot_mission_control_sandbox_remote_bypass","copilot_mission_control_task_alive_updates","copilot_mission_control_task_sharing","copilot_org_policy_page_focus_mode","copilot_share_active_subthread","copilot_spaces_ga","copilot_spaces_individual_policies_ga","copilot_swe_agent_authorization_status_ui","copilot_swe_agent_automation_resource_scoped_writes","copilot_swe_agent_discussion_comment_trigger","copilot_swe_agent_discussion_opened_trigger","copilot_swe_agent_discussion_updated_trigger","copilot_swe_agent_hide_model_picker_if_only_auto","copilot_swe_agent_issue_assigned_trigger","copilot_swe_agent_issue_comment_trigger","copilot_swe_agent_issue_labeled_trigger","copilot_swe_agent_pr_comment_model_picker","copilot_swe_agent_pull_request_assigned_trigger","copilot_swe_agent_pull_request_comment_trigger","copilot_swe_agent_pull_request_labeled_trigger","copilot_swe_agent_pull_request_merged_trigger","copilot_swe_agent_pull_request_opened_trigger","copilot_swe_agent_pull_request_ready_for_review_trigger","copilot_swe_agent_pull_request_review_requested_trigger","copilot_swe_agent_pull_request_review_submitted_trigger","copilot_swe_agent_pull_request_synchronize_trigger","copilot_swe_agent_sub_issue_added_trigger","copilot_swe_agent_use_subagents","copilot_task_api_github_rest_style","copilot_task_scoped_alive_channel","copilot_token_based_billing","copilot_unconfigured_is_inherited","copilot_user_can_upgrade_plan_field","copilot_web_integration_cutover","copilot_workbench_sunset_redirect","dashboard_agents_module_auth_token_check","dashboard_indexeddb_caching","fgpat_permissions_selector_redesign","glc_code_quality_repo_settings_workflow_config","hyperspace_2025_logged_out_batch_1","hyperspace_2025_logged_out_batch_2","hyperspace_2025_logged_out_batch_3","in_product_messaging_datadog_monitoring","ipm_ubb_individual_budget_banner","issue_fields_multi_select","issue_inline_avatars","issue_pinned_views","issue_pinned_views_team_vs_personal","issue_relative_time_micro","issue_viewer_paved_path","issues_expanded_file_types","issues_hide_closed_sub_issues","issues_lazy_load_comment_box_suggestions","issues_react_chrome_container_query_fix","labels_archiving","labels_archiving_info","landing_pages_ninetailed","lifecycle_label_name_updates","marketing_cookie_consent_banner","marketing_pages_search_explore_provider","memex_default_issue_create_repository","memex_live_update_hovercard","memex_mwl_filter_field_delimiter","memex_remove_deprecated_type_issue","merge_queue_restricted_pushers_warning","merge_status_header_feedback","milestone_closed_issues_prioritization","octocaptcha_origin_optimization","primer_react_css_anchor_positioning","primer_react_merged_forwarded_refs","primer_react_timeline_list_semantics","prs_copilot_app_open_action","prs_css_anchor_positioning","pull_request_copilot_attribution_header","pull_request_overview_merge_control","pull_request_overview_panel_edit_description","pull_request_persister","pull_request_stacks_navigation_shortcuts","pull_request_virtualization_image_estimate","pull_request_virtualization_loader_batching","pull_request_virtualization_scroll_compensation","pull_request_virtualization_scroll_intent","quick_search_lazy_suggestions","react_blob_isolate_code_lines","react_blob_ssr_content_visibility","react_data_router_tanstack_allowed","react_logged_out_repository_header","react_query_props_with_key","react_sandbox_future_tanstack","repo_app_turbo","repo_issues_sidebar_layout","repo_pulls_dashboard_declutter","repo_pulls_dashboard_ga","repo_pulls_dashboard_persistence","repos_contributors_limited_default_range","review_involves_filter","rule_ignored_file_paths","rulesets_actor_list_editor","sample_network_conn_type","security_center_artifact_filters_popover","see_who_reacted","semantic_similarity_duplicate_issue_detection","session_logs_ungroup_reasoning_text","set_sha256_on_repo_creation_form","site_banner_desktop_copilot_app","site_ghca_pixel_mona","site_github_app_ga_page","site_github_app_ga_page_highlight","site_github_app_mobile_native_share","site_global_banner_dev_days_attendee","site_global_nav_spark_models_removed","speculation_rules_ui_service","suggest_custom_property_values_copilot","suppress_automated_browser_vitals","swp_forms_disable_octocaptcha","thread_resolution_reason","update_issue_suggestions","viewscreen_sandbox","warn_inaccessible_attachments","webp_support","workstream_plugin_bootstrap"],"githubDomain":"https://github.com","copilotApiOverrideUrl":"https://api.githubcopilot.com","cmcApiUrl":"https://api.github.com/cmc_internal/api"}</script>
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/high-contrast-cookie-3663bcaacc724f4c.js"></script>
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/wp-runtime-721126455a2225e3.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/app-foundation-156c6259e31cc551.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/app-runtime-f6ce38f47c2e1d56.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/fetch-utilities-804a14996977c014.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ser-fb5d13278eb42203.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/tp-ea613009a991e211.js" />
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/environment-107a7753389a8a2d.js" defer="defer"></script>
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/app-runtime.25915bc98fb322a6.module.css" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/catalyst-52ed81112a548e10.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/selector-observer-e8810f64c443fb9b.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/relative-time-element-fe7e72f699fdf493.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/by-9da36843ba9d191d.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ja9-9a1160b939f2cd52.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/3h-55903cf2303047ad.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/hk-0d35a1220587d888.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/jz5-c964b1a3671f2a92.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/hj-1d800d09e9a8720a.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/j0-1ad12374da9777f2.js" />
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/github-elements-531d35538b61fd0d.js" defer="defer"></script>
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/element-registry-bf5e3b2aee197122.js" defer="defer"></script>
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/runtime-helpers-5e0b3ae3c4036207.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/aria-live-752acdc868e2da64.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/hotkey-e87ffadced20a3c9.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/react-core-75bc55d9971828b7.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/a9-34620c7b13885011.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ur-643d2814115561a9.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/4t6-5d0869970ac0aac0.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/pb0-a5c5ec36d311aeef.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/6n-afcf86ccabc4cad2.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/zj-f08c728cf00018aa.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/o6-2a1c68d269710ee9.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/h9a-c6405aa090de0e84.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/vy6-3c3e6e96629c2ce4.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/js-4eb97d5803e657c0.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/g7-baf9e817ae102b01.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/unc-96389cade7bad821.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/nu-65322a6997c6c93c.js" />
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/behaviors-5dd3809b5c8bac3c.js" defer="defer"></script>
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/react-core.3792087592bf207a.module.css" />
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/code-menu-4318f0f4c608f3dd.js" defer="defer"></script>
  
  <link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/primer-react-7afef75d393cf294.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/op-acddf38478d45ef9.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ncx-461e4b09f2e67ee5.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/qmp-c7cfba40cf5a93b0.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/j6-148a74299e5a01ab.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/t7l-a52ba56147f77909.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/4u-ec4874f70bfd5d76.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/s0-481bd34f90bc2bd7.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/y5-ed17e73b4c0bd15e.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/n4-be595475cdbf7d4b.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/j8-23a3c5ff8eef412d.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/la-27679061f16e1495.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/iu-9806a0291a782cbe.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ftx-39fb490e3c1d778f.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/zi-32a0c3c081ea5cce.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/sg-7a5d50475874c0e1.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/pd-6a8710840033e042.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/86g-ec28c25ed7d5de06.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/sn-1bab7384818953b7.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/iq-6d4ecf6d2fb6993c.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/p2-7d8e7cee5b46cb45.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/7d-2e3cc25d719f3a95.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/4d-76d8acdf21a33266.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/tsb-e0f6288ab985d9d2.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/lf-910ef220ce85c660.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/2j-2f19c1afd2ceaf93.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/me-f1476ec44a29f34a.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/p2u-e654a57b1a70c47d.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/5d-d97cc006d4da4d9b.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/r5-38f013bc2c0fd298.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/r6s-9039f5619230a653.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/9e-cece06724bf6ea3b.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/x9-3e077a3a37a73f9f.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/pj-87629cb5389d7663.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/eq-3764fd5f66494289.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/13-d889936dff7e5c0f.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/44-8193eea4af825ead.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/v4-9c1327d212e52a0d.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/id-7f39706ba9007db6.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ah-271362a1ed227d03.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/s1g-d434d712bca59d3a.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/vz-d7f4a583896f872e.js" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/47-c8c0ce2700acc5be.js" />
<script crossorigin="anonymous" type="module" src="https://github.githubassets.com/assets/code-view-4118a687d0150749.js" defer="defer"></script>
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-css.2d881b4d4a503d1c.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/pj.75627bfeb9ceb1da.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/ah.2623f7c6ecbe2ecd.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/47.78770c57a48a810c.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/code-view.37656c95a9729fb9.module.css" />


  <title>scada-mobile/AUTH_FLOW.md at main · gleb7499/scada-mobile · GitHub</title>



  <meta name="route-pattern" content="/:user_id/:repository/blob/*name(/*path)" data-turbo-transient>
  <meta name="route-controller" content="blob" data-turbo-transient>
  <meta name="route-action" content="show" data-turbo-transient>
  <meta name="fetch-nonce" content="v2:dbf8ae38-134a-4f7c-06c1-817d8a62e174">

    
  <meta name="current-catalog-service-hash" content="f3abb0cc802f3d7b95fc8762b94bdcb13bf39634c40c357301c4aa1d67a256fb">


  <meta name="request-id" content="A6D0:18F33C:B74B9AC:EEBA13E:6ACA72C3" data-pjax-transient="true"/><meta name="html-safe-nonce" content="78e67bcb792b917eeae767a907fbf60765b09956b0ff2e50065d8d74561091b2" data-pjax-transient="true"/><meta name="visitor-payload" content="eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJBNkQwOjE4RjMzQzpCNzRCOUFDOkVFQkExM0U6NkFDQTcyQzMiLCJ2aXNpdG9yX2lkIjoiNTAwODkwNTU0NjYwMzU5MDMzOSIsInJlZ2lvbl9lZGdlIjoiZnJhIiwicmVnaW9uX3JlbmRlciI6ImZyYSJ9" data-pjax-transient="true"/><meta name="visitor-hmac" content="fc388eeb50f1cb2abd49416a4b60e0a4aa5f23183eb2b130b5842b8bb9926e08" data-pjax-transient="true"/>


    <meta name="hovercard-subject-tag" content="repository:1189419502" data-turbo-transient>


  <meta name="github-keyboard-shortcuts" content="repository,source-code,file-tree,copilot" data-turbo-transient="true" />
  

  <meta name="selected-link" value="repo_source" data-turbo-transient>
  <link rel="assets" href="https://github.githubassets.com/">

    <meta name="google-site-verification" content="Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I">

<meta name="octolytics-url" content="https://collector.github.com/github/collect" />





  <meta name="analytics-location" content="/&lt;user-name&gt;/&lt;repo-name&gt;/blob/show" data-turbo-transient="true" />

  




    <meta name="user-login" content="">

  

    <meta name="viewport" content="width=device-width">

    

      <meta name="description" content="Модуль выполнения бизнес-логики для мобильного приложения SCADA - scada-mobile/AUTH_FLOW.md at main · gleb7499/scada-mobile">

      <link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="GitHub">

    <link rel="fluid-icon" href="https://github.com/fluidicon.png" title="GitHub">
    <meta property="fb:app_id" content="1401488693436528">
    <meta name="apple-itunes-app" content="app-id=1477376905, app-argument=https://github.com/gleb7499/scada-mobile/blob/main/AUTH_FLOW.md" />

      <meta name="twitter:image" content="https://opengraph.githubassets.com/f8aa35df9d452c86ec7e078936467c9a3e976f68727128210abbd93222287b92/gleb7499/scada-mobile" /><meta name="twitter:site" content="@github" /><meta name="twitter:card" content="summary_large_image" /><meta name="twitter:title" content="scada-mobile/AUTH_FLOW.md at main · gleb7499/scada-mobile" /><meta name="twitter:description" content="Модуль выполнения бизнес-логики для мобильного приложения SCADA - gleb7499/scada-mobile" />
  <meta property="og:image" content="https://opengraph.githubassets.com/f8aa35df9d452c86ec7e078936467c9a3e976f68727128210abbd93222287b92/gleb7499/scada-mobile" /><meta property="og:image:alt" content="Модуль выполнения бизнес-логики для мобильного приложения SCADA - gleb7499/scada-mobile" /><meta property="og:image:width" content="1200" /><meta property="og:image:height" content="600" /><meta property="og:site_name" content="GitHub" /><meta property="og:type" content="object" /><meta property="og:title" content="scada-mobile/AUTH_FLOW.md at main · gleb7499/scada-mobile" /><meta property="og:url" content="https://github.com/gleb7499/scada-mobile/blob/main/AUTH_FLOW.md" /><meta property="og:description" content="Модуль выполнения бизнес-логики для мобильного приложения SCADA - gleb7499/scada-mobile" />
  




      <meta name="hostname" content="github.com">



        <meta name="expected-hostname" content="github.com">


  <meta http-equiv="x-pjax-version" content="d05be8f8b6905b6460fc264e80447721dfee1e32cf9e4291c4bf58f160c9f705" data-turbo-track="reload">
  <meta http-equiv="x-pjax-csp-version" content="c4a65e47b0c850e1157ae8e66298070851d7e3b558038de5a3b4ff7a93e630ed" data-turbo-track="reload">
  <meta http-equiv="x-pjax-css-version" content="a80b0545b169f440b929ce2b977c76b6988f014cca6513fb83c5df6581635fb3" data-turbo-track="reload">
  <meta http-equiv="x-pjax-js-version" content="af51c6f9d659be0324665373df3ee542f817dbd7f66f352a087935f2ceabe545" data-turbo-track="reload">

  <meta name="turbo-cache-control" content="no-preview" data-turbo-transient="">

      <meta name="turbo-cache-control" content="no-cache" data-turbo-transient>

    <meta data-hydrostats="publish">

  <meta name="go-import" content="github.com/gleb7499/scada-mobile git https://github.com/gleb7499/scada-mobile.git">

  <meta name="octolytics-dimension-user_id" content="164905074" /><meta name="octolytics-dimension-user_login" content="gleb7499" /><meta name="octolytics-dimension-repository_id" content="1189419502" /><meta name="octolytics-dimension-repository_nwo" content="gleb7499/scada-mobile" /><meta name="octolytics-dimension-repository_public" content="true" /><meta name="octolytics-dimension-repository_is_fork" content="true" /><meta name="octolytics-dimension-repository_parent_id" content="1078098970" /><meta name="octolytics-dimension-repository_parent_nwo" content="savushkin-dev/scada-mobile" /><meta name="octolytics-dimension-repository_network_root_id" content="1078098970" /><meta name="octolytics-dimension-repository_network_root_nwo" content="savushkin-dev/scada-mobile" />
  



    

    <meta name="turbo-body-classes" content="logged-out env-production page-responsive">
  <meta name="disable-turbo" content="false">


  <meta name="browser-stats-url" content="https://api.github.com/_private/browser/stats">


  <meta name="browser-errors-url" content="https://api.github.com/_private/browser/errors">

  <meta name="release" content="3b1bb5b10f5c7fd1769d56e38b15dfddb34052a8" data-turbo-track="reload">
  <meta name="ui-target" content="full">

  <link rel="mask-icon" href="https://github.githubassets.com/assets/pinned-octocat-093da3e6fa40.svg" color="#000000">
  <link rel="alternate icon" class="js-site-favicon" type="image/png" href="https://github.githubassets.com/favicons/favicon.png">
  <link rel="icon" class="js-site-favicon" type="image/svg+xml" href="https://github.githubassets.com/favicons/favicon.svg" data-base-href="https://github.githubassets.com/favicons/favicon">

<meta name="theme-color" content="#1e2327">
<meta name="color-scheme" content="light dark" />


  <link rel="manifest" href="/manifest.json" crossOrigin="use-credentials">

  </head>

  <body class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
    <div data-turbo-body class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
      <div id="__primerPortalRoot__" style="z-index: 1000; position: absolute; width: 100%;" data-turbo-permanent></div>
      

    <div class="position-relative header-wrapper js-header-wrapper ">
      <a href="#start-of-content" data-skip-target-assigned="false" class="px-2 tmp-py-4 color-bg-accent-emphasis color-fg-on-emphasis show-on-focus js-skip-to-content">Skip to content</a>

      <span data-view-component="true" class="progress-pjax-loader Progress position-fixed width-full">
    <span style="width: 0%;" data-view-component="true" class="Progress-item progress-pjax-loader-bar left-0 top-0 color-bg-accent-emphasis"></span>
</span>      
      <link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/19-c660652a0a1639be.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/keyboard-shortcuts-dialog-47c5223d8d1af8ef.js" fetchpriority="low" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-css.2d881b4d4a503d1c.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/keyboard-shortcuts-dialog.d53d993320d57e14.module.css" />

<react-partial
  partial-name="keyboard-shortcuts-dialog"
  data-ssr="false"
  data-attempted-ssr="false"
  data-react-profiling="false"
>
  
  <script type="application/json" data-target="react-partial.embeddedData">{"props":{"docsUrl":"https://docs.github.com/get-started/accessibility/keyboard-shortcuts"}}</script>
  <div data-target="react-partial.reactRoot"></div>
</react-partial>





      

          <link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/app-install-banner-partial-3c87deb7f0f20434.js" fetchpriority="low" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-css.2d881b4d4a503d1c.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/app-install-banner-partial.7b357012e82003e7.module.css" />

<react-partial
  partial-name="app-install-banner-partial"
  data-ssr="false"
  data-attempted-ssr="false"
  data-react-profiling="false"
>
  
  <script type="application/json" data-target="react-partial.embeddedData">{"props":{}}</script>
  <div data-target="react-partial.reactRoot"></div>
</react-partial>


          

                <link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/et9-7eeec247c287fca4.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/94v-e20946127faa8f6e.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/fz5-892a9e99cc92858d.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/jag-441e702bfd4c7904.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/hw-8ce7888b57d0a23c.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/rq-0ec82a12cf538a4d.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/1m-a7a4c022483b2713.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/marketing-header-70703fe17422eb58.js" fetchpriority="low" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-css.2d881b4d4a503d1c.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-brand-css.fbc35b3d20e988ff.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/1m.fec8b575d0e5234f.module.css" />

<react-partial
  partial-name="marketing-header"
  data-ssr="true"
  data-attempted-ssr="true"
  data-react-profiling="false"
>
  
  <script type="application/json" data-target="react-partial.embeddedData">{"props":{"color_mode":"dark","logged_in":false,"marketing_page":false,"home_path":"/","login_path":"/login?return_to=https%3A%2F%2Fgithub.com%2Fgleb7499%2Fscada-mobile%2Fblob%2Fmain%2FAUTH_FLOW.md","signup_path":"/signup?ref_cta=Sign+up\u0026ref_loc=header+logged+out\u0026ref_page=%2F%3Cuser-name%3E%2F%3Crepo-name%3E%2Fblob%2Fshow\u0026source=header-repo\u0026source_repo=gleb7499%2Fscada-mobile","signup_enabled":true,"is_signup_controller":false,"show_search_and_nav":true,"hide_search":false,"private_mode_enabled":false,"should_use_dotcom_links":true,"auth_hydro_click":"{\"event_type\":\"authentication.click\",\"payload\":{\"location_in_page\":\"site header menu\",\"repository_id\":null,\"auth_type\":\"SIGN_UP\",\"originating_url\":\"https://github.com/gleb7499/scada-mobile/blob/main/AUTH_FLOW.md\",\"user_id\":null}}","auth_hydro_click_hmac":"ce3b534c9b44bf8ccaf1aca02d708edb1f848d66a53595ca6bda592b01e43d83","overlay":false,"fixed":false}}</script>
  <div data-target="react-partial.reactRoot"><div data-color-mode="dark" data-light-theme="light" data-dark-theme="dark"><header class="MarketingHeader-module__root__Tk7n3 HeaderMktg header-logged-out" role="banner" data-marketing-header="true" data-color-mode="dark" data-light-theme="light" data-dark-theme="dark" data-is-top="true"><h2 class="MarketingHeader-module__visuallyHidden__sqKsl">Navigation Menu</h2><button type="button" class="MarketingHeader-module__backdrop__sw4RU" aria-label="Close navigation menu"></button><div class="MarketingHeader-module__bar__mBSyE"><div class="MarketingHeader-module__topRow__yeury"><div class="MarketingHeader-module__toggleSlot__hDxbh"><button type="button" class="HeaderMenuToggle-module__toggle__i8EiC" aria-label="Toggle navigation" aria-expanded="false"><span class="HeaderMenuToggle-module__toggleBar__jVN0H"></span><span class="HeaderMenuToggle-module__toggleBar__jVN0H"></span><span class="HeaderMenuToggle-module__toggleBar__jVN0H"></span></button></div><a href="/" aria-label="Homepage" class="HeaderLogo-module__logo__UFyHI" data-analytics-event="{&quot;action&quot;:&quot;homepage&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;logo&quot;,&quot;location&quot;:&quot;header&quot;,&quot;label&quot;:&quot;homepage_link_logo_header&quot;}"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-mark-github" viewBox="0 0 24 24" width="32" height="32" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M10.226 17.284c-2.965-.36-5.054-2.493-5.054-5.256 0-1.123.404-2.336 1.078-3.144-.292-.741-.247-2.314.09-2.965.898-.112 2.111.36 2.83 1.01.853-.269 1.752-.404 2.853-.404 1.1 0 1.999.135 2.807.382.696-.629 1.932-1.1 2.83-.988.315.606.36 2.179.067 2.942.72.854 1.101 2 1.101 3.167 0 2.763-2.089 4.852-5.098 5.234.763.494 1.28 1.572 1.28 2.807v2.336c0 .674.561 1.056 1.235.786 4.066-1.55 7.255-5.615 7.255-10.646C23.5 6.188 18.334 1 11.978 1 5.62 1 .5 6.188.5 12.545c0 4.986 3.167 9.12 7.435 10.669.606.225 1.19-.18 1.19-.786V20.63a2.9 2.9 0 0 1-1.078.224c-1.483 0-2.359-.808-2.987-2.313-.247-.607-.517-.966-1.034-1.033-.27-.023-.359-.135-.359-.27 0-.27.45-.471.898-.471.652 0 1.213.404 1.797 1.235.45.651.921.943 1.483.943.561 0 .92-.202 1.437-.719.382-.381.674-.718.944-.943"></path></svg></a><div class="AuthCTAs-module__mobileActions__NNzeV"><a class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--subtle___F7pEE Primer_Brand__Button-module__Button--size-small___zQrEw AuthCTAs-module__cta__WpwQq" href="/login?return_to=https%3A%2F%2Fgithub.com%2Fgleb7499%2Fscada-mobile%2Fblob%2Fmain%2FAUTH_FLOW.md" data-analytics-event="{&quot;action&quot;:&quot;sign_in&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;auth_cta&quot;,&quot;location&quot;:&quot;header&quot;,&quot;label&quot;:&quot;sign_in_link_auth_cta_header&quot;}" data-hydro-click="{&quot;event_type&quot;:&quot;authentication.click&quot;,&quot;payload&quot;:{&quot;location_in_page&quot;:&quot;site header menu&quot;,&quot;repository_id&quot;:null,&quot;auth_type&quot;:&quot;SIGN_UP&quot;,&quot;originating_url&quot;:&quot;https://github.com/gleb7499/scada-mobile/blob/main/AUTH_FLOW.md&quot;,&quot;user_id&quot;:null}}" data-hydro-click-hmac="ce3b534c9b44bf8ccaf1aca02d708edb1f848d66a53595ca6bda592b01e43d83"><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-subtle___8ndWH">Sign in</span></span></a><button class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--subtle___F7pEE Primer_Brand__Button-module__Button--size-small___zQrEw HeaderAppearanceSettings-module__trigger__hUheK" type="button" aria-haspopup="dialog" aria-labelledby="_R_3dd_"><span class="Primer_Brand__Button-module__Button__leading-visual___jjtTe" data-testid="Button-leading-visual"><svg data-component="Octicon" focusable="false" aria-hidden="true" class="octicon octicon-sliders Primer_Brand__Button-module__Button__icon-visual____qybb" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M15 2.75a.75.75 0 0 1-.75.75h-4a.75.75 0 0 1 0-1.5h4a.75.75 0 0 1 .75.75Zm-8.5.75v1.25a.75.75 0 0 0 1.5 0v-4a.75.75 0 0 0-1.5 0V2H1.75a.75.75 0 0 0 0 1.5H6.5Zm1.25 5.25a.75.75 0 0 0 0-1.5h-6a.75.75 0 0 0 0 1.5h6ZM15 8a.75.75 0 0 1-.75.75H11.5V10a.75.75 0 1 1-1.5 0V6a.75.75 0 0 1 1.5 0v1.25h2.75A.75.75 0 0 1 15 8Zm-9 5.25v-2a.75.75 0 0 0-1.5 0v1.25H1.75a.75.75 0 0 0 0 1.5H4.5v1.25a.75.75 0 0 0 1.5 0v-2Zm9 0a.75.75 0 0 1-.75.75h-6a.75.75 0 0 1 0-1.5h6a.75.75 0 0 1 .75.75Z"></path></svg></span><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-subtle___8ndWH"></span></span></button><div class="Primer_Brand__Tooltip-module__Tooltip___0Eipx" data-direction="s" aria-hidden="true" id="_R_3dd_">Appearance settings</div></div></div><div class="MarketingHeader-module__menu__GIy3y"><div class="MarketingHeader-module__menuWrapper__owstH"><nav class="MarketingNavigation-module__nav__W0KYY" aria-label="Global"><ul class="MarketingNavigation-module__list__tFbMb"><li><div class="NavDropdown-module__container__l2YeI"><button type="button" class="NavDropdown-module__button__PEHWX" aria-expanded="false" aria-controls="_R_nd_">Platform<svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-right NavDropdown-module__buttonIcon__Tkl8_" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m6.427 4.427 3.396 3.396a.25.25 0 0 1 0 .354l-3.396 3.396A.25.25 0 0 1 6 11.396V4.604a.25.25 0 0 1 .427-.177Z"></path></svg></button><div id="_R_nd_" class="NavDropdown-module__dropdown__xm1jd"><ul class="NavDropdown-module__list__zuCgG"><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_5knd_">AI CODE CREATION</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_5knd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/copilot" data-analytics-event="{&quot;action&quot;:&quot;github_copilot&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_copilot_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-copilot NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M7.998 15.035c-4.562 0-7.873-2.914-7.998-3.749V9.338c.085-.628.677-1.686 1.588-2.065.013-.07.024-.143.036-.218.029-.183.06-.384.126-.612-.201-.508-.254-1.084-.254-1.656 0-.87.128-1.769.693-2.484.579-.733 1.494-1.124 2.724-1.261 1.206-.134 2.262.034 2.944.765.05.053.096.108.139.165.044-.057.094-.112.143-.165.682-.731 1.738-.899 2.944-.765 1.23.137 2.145.528 2.724 1.261.566.715.693 1.614.693 2.484 0 .572-.053 1.148-.254 1.656.066.228.098.429.126.612.012.076.024.148.037.218.924.385 1.522 1.471 1.591 2.095v1.872c0 .766-3.351 3.795-8.002 3.795Zm0-1.485c2.28 0 4.584-1.11 5.002-1.433V7.862l-.023-.116c-.49.21-1.075.291-1.727.291-1.146 0-2.059-.327-2.71-.991A3.222 3.222 0 0 1 8 6.303a3.24 3.24 0 0 1-.544.743c-.65.664-1.563.991-2.71.991-.652 0-1.236-.081-1.727-.291l-.023.116v4.255c.419.323 2.722 1.433 5.002 1.433ZM6.762 2.83c-.193-.206-.637-.413-1.682-.297-1.019.113-1.479.404-1.713.7-.247.312-.369.789-.369 1.554 0 .793.129 1.171.308 1.371.162.181.519.379 1.442.379.853 0 1.339-.235 1.638-.54.315-.322.527-.827.617-1.553.117-.935-.037-1.395-.241-1.614Zm4.155-.297c-1.044-.116-1.488.091-1.681.297-.204.219-.359.679-.242 1.614.091.726.303 1.231.618 1.553.299.305.784.54 1.638.54.922 0 1.28-.198 1.442-.379.179-.2.308-.578.308-1.371 0-.765-.123-1.242-.37-1.554-.233-.296-.693-.587-1.713-.7Z"></path><path d="M6.25 9.037a.75.75 0 0 1 .75.75v1.501a.75.75 0 0 1-1.5 0V9.787a.75.75 0 0 1 .75-.75Zm4.25.75v1.501a.75.75 0 0 1-1.5 0V9.787a.75.75 0 0 1 1.5 0Z"></path></svg>GitHub Copilot</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Write better code with AI</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/ai/github-app" data-analytics-event="{&quot;action&quot;:&quot;github_copilot_app&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_copilot_app_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-mark-github NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M6.766 11.328c-2.063-.25-3.516-1.734-3.516-3.656 0-.781.281-1.625.75-2.188-.203-.515-.172-1.609.063-2.062.625-.078 1.468.25 1.968.703.594-.187 1.219-.281 1.985-.281.765 0 1.39.094 1.953.265.484-.437 1.344-.765 1.969-.687.218.422.25 1.515.046 2.047.5.593.766 1.39.766 2.203 0 1.922-1.453 3.375-3.547 3.64.531.344.89 1.094.89 1.954v1.625c0 .468.391.734.86.547C13.781 14.359 16 11.53 16 8.03 16 3.61 12.406 0 7.984 0 3.563 0 0 3.61 0 8.031a7.88 7.88 0 0 0 5.172 7.422c.422.156.828-.125.828-.547v-1.25c-.219.094-.5.156-.75.156-1.031 0-1.64-.562-2.078-1.609-.172-.422-.36-.672-.719-.719-.187-.015-.25-.093-.25-.187 0-.188.313-.328.625-.328.453 0 .844.281 1.25.86.313.452.64.655 1.031.655s.641-.14 1-.5c.266-.265.47-.5.657-.656"></path></svg>GitHub Copilot app</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Direct agents from issue to merge</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/mcp" data-analytics-event="{&quot;action&quot;:&quot;mcp_registry&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;mcp_registry_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-mcp NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M5.52 1.12a3.578 3.578 0 0 1 6.078 2.98 3.578 3.578 0 0 1 2.982 6.08l-3.292 3.293a.252.252 0 0 0 0 .354l.843.843a.749.749 0 1 1-1.06 1.06l-.844-.843a1.75 1.75 0 0 1 0-2.474L13.52 9.12a2.08 2.08 0 0 0 0-2.94 2.08 2.08 0 0 0-2.94 0L7.731 9.03A.75.75 0 0 1 6.67 7.97l2.85-2.85a2.08 2.08 0 0 0 0-2.94 2.08 2.08 0 0 0-2.94 0l-4.799 4.8A.75.75 0 0 1 .72 5.92Z"></path><path d="M7.52 3.12a.749.749 0 1 1 1.06 1.06L5.731 7.03A2.079 2.079 0 0 0 8.67 9.97l2.85-2.85a.749.749 0 1 1 1.06 1.06l-2.849 2.85A3.578 3.578 0 0 1 4.67 5.97Z"></path></svg>MCP Registry</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Integrate external tools</span></span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_9knd_">DEVELOPER WORKFLOWS</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_9knd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/actions" data-analytics-event="{&quot;action&quot;:&quot;actions&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;actions_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-workflow NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 1.75C0 .784.784 0 1.75 0h3.5C6.216 0 7 .784 7 1.75v3.5A1.75 1.75 0 0 1 5.25 7H4v4a1 1 0 0 0 1 1h4v-1.25C9 9.784 9.784 9 10.75 9h3.5c.966 0 1.75.784 1.75 1.75v3.5A1.75 1.75 0 0 1 14.25 16h-3.5A1.75 1.75 0 0 1 9 14.25v-.75H5A2.5 2.5 0 0 1 2.5 11V7h-.75A1.75 1.75 0 0 1 0 5.25Zm1.75-.25a.25.25 0 0 0-.25.25v3.5c0 .138.112.25.25.25h3.5a.25.25 0 0 0 .25-.25v-3.5a.25.25 0 0 0-.25-.25Zm9 9a.25.25 0 0 0-.25.25v3.5c0 .138.112.25.25.25h3.5a.25.25 0 0 0 .25-.25v-3.5a.25.25 0 0 0-.25-.25Z"></path></svg>Actions</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Automate any workflow</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/codespaces" data-analytics-event="{&quot;action&quot;:&quot;codespaces&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;codespaces_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-codespaces NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 11.25c0-.966.784-1.75 1.75-1.75h12.5c.966 0 1.75.784 1.75 1.75v3A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25Zm2-9.5C2 .784 2.784 0 3.75 0h8.5C13.216 0 14 .784 14 1.75v5a1.75 1.75 0 0 1-1.75 1.75h-8.5A1.75 1.75 0 0 1 2 6.75Zm1.75-.25a.25.25 0 0 0-.25.25v5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-5a.25.25 0 0 0-.25-.25Zm-2 9.5a.25.25 0 0 0-.25.25v3c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25v-3a.25.25 0 0 0-.25-.25Z"></path><path d="M7 12.75a.75.75 0 0 1 .75-.75h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1-.75-.75Zm-4 0a.75.75 0 0 1 .75-.75h.5a.75.75 0 0 1 0 1.5h-.5a.75.75 0 0 1-.75-.75Z"></path></svg>Codespaces</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Instant dev environments</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/issues" data-analytics-event="{&quot;action&quot;:&quot;issues&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;issues_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-issue-opened NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path><path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Z"></path></svg>Issues</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Plan and track work</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/code-review" data-analytics-event="{&quot;action&quot;:&quot;code_review&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;code_review_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-code NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m11.28 3.22 4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L13.94 8l-3.72-3.72a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215Zm-6.56 0a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042L2.06 8l3.72 3.72a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L.47 8.53a.75.75 0 0 1 0-1.06Z"></path></svg>Code Review</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Manage code changes</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/code-quality" data-analytics-event="{&quot;action&quot;:&quot;code_quality&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;code_quality_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-codescan-checkmark NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M10.28 6.28a.75.75 0 1 0-1.06-1.06L6.25 8.19l-.97-.97a.75.75 0 0 0-1.06 1.06l1.5 1.5a.75.75 0 0 0 1.06 0l3.5-3.5Z"></path><path d="M7.5 15a7.5 7.5 0 1 1 5.807-2.754l2.473 2.474a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215l-2.474-2.473A7.472 7.472 0 0 1 7.5 15Zm0-13.5a6 6 0 1 0 4.094 10.386.748.748 0 0 1 .293-.292 6.002 6.002 0 0 0 1.117-6.486A6.002 6.002 0 0 0 7.5 1.5Z"></path></svg>Code Quality</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Enforce quality at merge</span></span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_dknd_">APPLICATION SECURITY</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_dknd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/security/advanced-security" data-analytics-event="{&quot;action&quot;:&quot;github_advanced_security&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_advanced_security_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-shield-check NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m8.533.133 5.25 1.68A1.75 1.75 0 0 1 15 3.48V7c0 1.566-.32 3.182-1.303 4.682-.983 1.498-2.585 2.813-5.032 3.855a1.697 1.697 0 0 1-1.33 0c-2.447-1.042-4.049-2.357-5.032-3.855C1.32 10.182 1 8.566 1 7V3.48a1.75 1.75 0 0 1 1.217-1.667l5.25-1.68a1.748 1.748 0 0 1 1.066 0Zm-.61 1.429.001.001-5.25 1.68a.251.251 0 0 0-.174.237V7c0 1.36.275 2.666 1.057 3.859.784 1.194 2.121 2.342 4.366 3.298a.196.196 0 0 0 .154 0c2.245-.957 3.582-2.103 4.366-3.297C13.225 9.666 13.5 8.358 13.5 7V3.48a.25.25 0 0 0-.174-.238l-5.25-1.68a.25.25 0 0 0-.153 0ZM11.28 6.28l-3.5 3.5a.75.75 0 0 1-1.06 0l-1.5-1.5a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l.97.97 2.97-2.97a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"></path></svg>GitHub Advanced Security</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Find and fix vulnerabilities</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/security/advanced-security/code-security" data-analytics-event="{&quot;action&quot;:&quot;code_security&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;code_security_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-code-square NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25Zm1.75-.25a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25V1.75a.25.25 0 0 0-.25-.25Zm7.47 3.97a.75.75 0 0 1 1.06 0l2 2a.75.75 0 0 1 0 1.06l-2 2a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L10.69 8 9.22 6.53a.75.75 0 0 1 0-1.06ZM6.78 6.53 5.31 8l1.47 1.47a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215l-2-2a.75.75 0 0 1 0-1.06l2-2a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"></path></svg>Code security</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Secure your code as you build</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/security/advanced-security/secret-protection" data-analytics-event="{&quot;action&quot;:&quot;secret_protection&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;secret_protection_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-lock NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M4 4a4 4 0 0 1 8 0v2h.25c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 12.25 15h-8.5A1.75 1.75 0 0 1 2 13.25v-5.5C2 6.784 2.784 6 3.75 6H4Zm8.25 3.5h-8.5a.25.25 0 0 0-.25.25v5.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25ZM10.5 6V4a2.5 2.5 0 1 0-5 0v2Z"></path></svg>Secret protection</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Stop leaks before they start</span></span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ NavGroup-module__hasSeparator__FnMrN"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_hknd_">EXPLORE</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_hknd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/why-github" data-analytics-event="{&quot;action&quot;:&quot;why_github&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;why_github_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Why GitHub</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://docs.github.com" data-analytics-event="{&quot;action&quot;:&quot;documentation&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;documentation_link_platform_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Documentation</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://github.blog" data-analytics-event="{&quot;action&quot;:&quot;blog&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;blog_link_platform_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Blog</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://github.blog/changelog" data-analytics-event="{&quot;action&quot;:&quot;changelog&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;changelog_link_platform_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Changelog</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/marketplace" data-analytics-event="{&quot;action&quot;:&quot;marketplace&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;marketplace_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Marketplace</span></a></li></ul></div></li></ul><div class="NavDropdown-module__trailingLinkContainer__VgJGL"><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/features" data-analytics-event="{&quot;action&quot;:&quot;view_all_features&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;platform&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_features_link_platform_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all features</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></div></div></div></li><li><div class="NavDropdown-module__container__l2YeI"><button type="button" class="NavDropdown-module__button__PEHWX" aria-expanded="false" aria-controls="_R_17d_">Solutions<svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-right NavDropdown-module__buttonIcon__Tkl8_" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m6.427 4.427 3.396 3.396a.25.25 0 0 1 0 .354l-3.396 3.396A.25.25 0 0 1 6 11.396V4.604a.25.25 0 0 1 .427-.177Z"></path></svg></button><div id="_R_17d_" class="NavDropdown-module__dropdown__xm1jd"><ul class="NavDropdown-module__list__zuCgG"><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_5l7d_">BY COMPANY SIZE</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_5l7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/enterprise" data-analytics-event="{&quot;action&quot;:&quot;enterprises&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;enterprises_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Enterprises</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/team" data-analytics-event="{&quot;action&quot;:&quot;small_and_medium_teams&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;small_and_medium_teams_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Small and medium teams</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/enterprise/startups" data-analytics-event="{&quot;action&quot;:&quot;startups&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;startups_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Startups</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/industry/nonprofits" data-analytics-event="{&quot;action&quot;:&quot;nonprofits&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;nonprofits_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Nonprofits</span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_9l7d_">BY USE CASE</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_9l7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/use-case/app-modernization" data-analytics-event="{&quot;action&quot;:&quot;app_modernization&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;app_modernization_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">App Modernization</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/use-case/devsecops" data-analytics-event="{&quot;action&quot;:&quot;devsecops&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;devsecops_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">DevSecOps</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/use-case/devops" data-analytics-event="{&quot;action&quot;:&quot;devops&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;devops_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">DevOps</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/use-case/ci-cd" data-analytics-event="{&quot;action&quot;:&quot;ci/cd&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;ci/cd_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">CI/CD</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/solutions/use-case" data-analytics-event="{&quot;action&quot;:&quot;view_all_use_cases&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_use_cases_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all use cases</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_dl7d_">BY INDUSTRY</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_dl7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/industry/healthcare" data-analytics-event="{&quot;action&quot;:&quot;healthcare&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;healthcare_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Healthcare</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/industry/financial-services" data-analytics-event="{&quot;action&quot;:&quot;financial_services&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;financial_services_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Financial services</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/industry/manufacturing" data-analytics-event="{&quot;action&quot;:&quot;manufacturing&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;manufacturing_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Manufacturing</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/industry/government" data-analytics-event="{&quot;action&quot;:&quot;government&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;government_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Government</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/solutions/industry" data-analytics-event="{&quot;action&quot;:&quot;view_all_industries&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_industries_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all industries</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></li></ul></div></li></ul><div class="NavDropdown-module__trailingLinkContainer__VgJGL"><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/solutions" data-analytics-event="{&quot;action&quot;:&quot;view_all_solutions&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;solutions&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_solutions_link_solutions_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all solutions</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></div></div></div></li><li><div class="NavDropdown-module__container__l2YeI"><button type="button" class="NavDropdown-module__button__PEHWX" aria-expanded="false" aria-controls="_R_1nd_">Resources<svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-right NavDropdown-module__buttonIcon__Tkl8_" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m6.427 4.427 3.396 3.396a.25.25 0 0 1 0 .354l-3.396 3.396A.25.25 0 0 1 6 11.396V4.604a.25.25 0 0 1 .427-.177Z"></path></svg></button><div id="_R_1nd_" class="NavDropdown-module__dropdown__xm1jd"><ul class="NavDropdown-module__list__zuCgG"><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_5lnd_">EXPLORE BY TOPIC</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_5lnd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/articles?topic=ai" data-analytics-event="{&quot;action&quot;:&quot;ai&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;ai_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">AI</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/articles?topic=software-development" data-analytics-event="{&quot;action&quot;:&quot;software_development&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;software_development_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Software Development</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/articles?topic=devops" data-analytics-event="{&quot;action&quot;:&quot;devops&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;devops_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">DevOps</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/articles?topic=security" data-analytics-event="{&quot;action&quot;:&quot;security&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;security_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Security</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/resources/articles" data-analytics-event="{&quot;action&quot;:&quot;view_all_topics&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_topics_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all topics</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_9lnd_">EXPLORE BY TYPE</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_9lnd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/customer-stories" data-analytics-event="{&quot;action&quot;:&quot;customer_stories&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;customer_stories_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Customer stories</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/events" data-analytics-event="{&quot;action&quot;:&quot;events__webinars&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;events__webinars_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Events &amp; webinars</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/resources/whitepapers" data-analytics-event="{&quot;action&quot;:&quot;ebooks__reports&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;ebooks__reports_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Ebooks &amp; reports</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/solutions/executive-insights" data-analytics-event="{&quot;action&quot;:&quot;business_insights&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;business_insights_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Business insights</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://skills.github.com" data-analytics-event="{&quot;action&quot;:&quot;github_skills&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_skills_link_resources_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">GitHub Skills</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_dlnd_">SUPPORT &amp; SERVICES</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_dlnd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://docs.github.com" data-analytics-event="{&quot;action&quot;:&quot;documentation&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;documentation_link_resources_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Documentation</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://support.github.com" data-analytics-event="{&quot;action&quot;:&quot;customer_support&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;customer_support_link_resources_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Customer support</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/orgs/community/discussions" data-analytics-event="{&quot;action&quot;:&quot;community_forum&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;community_forum_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Community forum</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/trust-center" data-analytics-event="{&quot;action&quot;:&quot;trust_center&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;trust_center_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Trust center</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/partners" data-analytics-event="{&quot;action&quot;:&quot;partners&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;partners_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Partners</span></a></li></ul></div></li></ul><div class="NavDropdown-module__trailingLinkContainer__VgJGL"><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--arrow-end___esdN8" href="https://github.com/resources" data-analytics-event="{&quot;action&quot;:&quot;view_all_resources&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;resources&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;view_all_resources_link_resources_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">View all resources</span><svg class="Primer_Brand__ExpandableArrow-module__ExpandableArrow___aaZs9 Primer_Brand__Link-module__Link-arrow___yd78i" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false"><path fill="currentColor" d="M7.28033 3.21967C6.98744 2.92678 6.51256 2.92678 6.21967 3.21967C5.92678 3.51256 5.92678 3.98744 6.21967 4.28033L7.28033 3.21967ZM11 8L11.5303 8.53033C11.8232 8.23744 11.8232 7.76256 11.5303 7.46967L11 8ZM6.21967 11.7197C5.92678 12.0126 5.92678 12.4874 6.21967 12.7803C6.51256 13.0732 6.98744 13.0732 7.28033 12.7803L6.21967 11.7197ZM6.21967 4.28033L10.4697 8.53033L11.5303 7.46967L7.28033 3.21967L6.21967 4.28033ZM10.4697 7.46967L6.21967 11.7197L7.28033 12.7803L11.5303 8.53033L10.4697 7.46967Z"></path><path class="Primer_Brand__ExpandableArrow-module__ExpandableArrow-stem___0K8Hz" stroke="currentColor" d="M1.75 8H11" stroke-width="1.5" stroke-linecap="round"></path></svg></a></div></div></div></li><li><div class="NavDropdown-module__container__l2YeI"><button type="button" class="NavDropdown-module__button__PEHWX" aria-expanded="false" aria-controls="_R_27d_">Open Source<svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-right NavDropdown-module__buttonIcon__Tkl8_" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m6.427 4.427 3.396 3.396a.25.25 0 0 1 0 .354l-3.396 3.396A.25.25 0 0 1 6 11.396V4.604a.25.25 0 0 1 .427-.177Z"></path></svg></button><div id="_R_27d_" class="NavDropdown-module__dropdown__xm1jd"><ul class="NavDropdown-module__list__zuCgG"><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_5m7d_">COMMUNITY</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_5m7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/open-source/sponsors" data-analytics-event="{&quot;action&quot;:&quot;github_sponsors&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_sponsors_link_open_source_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-sponsor-tiers NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M10.586 1C12.268 1 13.5 2.37 13.5 4.25c0 1.745-.996 3.359-2.622 4.831-.166.15-.336.297-.509.438l1.116 5.584a.75.75 0 0 1-.991.852l-2.409-.876a.25.25 0 0 0-.17 0l-2.409.876a.75.75 0 0 1-.991-.852L5.63 9.519a13.78 13.78 0 0 1-.51-.438C3.497 7.609 2.5 5.995 2.5 4.25 2.5 2.37 3.732 1 5.414 1c.963 0 1.843.403 2.474 1.073L8 2.198l.112-.125a3.385 3.385 0 0 1 2.283-1.068L10.586 1Zm-3.621 9.495-.718 3.594 1.155-.42a1.75 1.75 0 0 1 1.028-.051l.168.051 1.154.42-.718-3.592c-.199.13-.37.235-.505.314l-.169.097a.75.75 0 0 1-.72 0 9.54 9.54 0 0 1-.515-.308l-.16-.105ZM10.586 2.5c-.863 0-1.611.58-1.866 1.459-.209.721-1.231.721-1.44 0C7.025 3.08 6.277 2.5 5.414 2.5 4.598 2.5 4 3.165 4 4.25c0 1.23.786 2.504 2.128 3.719.49.443 1.018.846 1.546 1.198l.325.21.076-.047.251-.163a13.341 13.341 0 0 0 1.546-1.198C11.214 6.754 12 5.479 12 4.25c0-1.085-.598-1.75-1.414-1.75Z"></path></svg>GitHub Sponsors</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Fund open source developers</span></span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_9m7d_">PROGRAMS</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_9m7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://securitylab.github.com" data-analytics-event="{&quot;action&quot;:&quot;security_lab&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;security_lab_link_open_source_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Security Lab</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://maintainers.github.com" data-analytics-event="{&quot;action&quot;:&quot;maintainer_community&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;maintainer_community_link_open_source_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Maintainer Community</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://stars.github.com" data-analytics-event="{&quot;action&quot;:&quot;github_stars&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_stars_link_open_source_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">GitHub Stars</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 Primer_Brand__Link-module__Link--is-external___xsncV" href="https://archiveprogram.github.com" data-analytics-event="{&quot;action&quot;:&quot;archive_program&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;archive_program_link_open_source_navbar&quot;}" target="_blank" rel="noreferrer"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Archive Program</span><svg data-component="Octicon" focusable="false" aria-label="External link" class="octicon octicon-link-external" role="img" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z"></path></svg></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_dm7d_">REPOSITORIES</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_dm7d_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/topics" data-analytics-event="{&quot;action&quot;:&quot;topics&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;topics_link_open_source_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Topics</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/trending" data-analytics-event="{&quot;action&quot;:&quot;trending&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;trending_link_open_source_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Trending</span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0" href="https://github.com/collections" data-analytics-event="{&quot;action&quot;:&quot;collections&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;open_source&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;collections_link_open_source_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty">Collections</span></a></li></ul></div></li></ul></div></div></li><li><div class="NavDropdown-module__container__l2YeI"><button type="button" class="NavDropdown-module__button__PEHWX" aria-expanded="false" aria-controls="_R_2nd_">Enterprise<svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-right NavDropdown-module__buttonIcon__Tkl8_" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m6.427 4.427 3.396 3.396a.25.25 0 0 1 0 .354l-3.396 3.396A.25.25 0 0 1 6 11.396V4.604a.25.25 0 0 1 .427-.177Z"></path></svg></button><div id="_R_2nd_" class="NavDropdown-module__dropdown__xm1jd"><ul class="NavDropdown-module__list__zuCgG"><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_5mnd_">ENTERPRISE SOLUTIONS</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_5mnd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/enterprise" data-analytics-event="{&quot;action&quot;:&quot;enterprise_platform&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;enterprise&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;enterprise_platform_link_enterprise_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-stack NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M7.122.392a1.75 1.75 0 0 1 1.756 0l5.003 2.902c.83.481.83 1.68 0 2.162L8.878 8.358a1.75 1.75 0 0 1-1.756 0L2.119 5.456a1.251 1.251 0 0 1 0-2.162ZM8.125 1.69a.248.248 0 0 0-.25 0l-4.63 2.685 4.63 2.685a.248.248 0 0 0 .25 0l4.63-2.685ZM1.601 7.789a.75.75 0 0 1 1.025-.273l5.249 3.044a.248.248 0 0 0 .25 0l5.249-3.044a.75.75 0 0 1 .752 1.298l-5.248 3.044a1.75 1.75 0 0 1-1.756 0L1.874 8.814A.75.75 0 0 1 1.6 7.789Zm0 3.5a.75.75 0 0 1 1.025-.273l5.249 3.044a.248.248 0 0 0 .25 0l5.249-3.044a.75.75 0 0 1 .752 1.298l-5.248 3.044a1.75 1.75 0 0 1-1.756 0l-5.248-3.044a.75.75 0 0 1-.273-1.025Z"></path></svg>Enterprise platform</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">AI-powered developer platform</span></span></a></li></ul></div></li><li><div class="NavGroup-module__group__W8SqJ"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--monospace___QXHDQ Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavGroup-module__title__Wzxz2" id="_R_9mnd_">AVAILABLE ADD-ONS</span><ul class="NavGroup-module__list__UCOFy" aria-labelledby="_R_9mnd_"><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/security/advanced-security" data-analytics-event="{&quot;action&quot;:&quot;github_advanced_security&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;enterprise&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;github_advanced_security_link_enterprise_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-shield-check NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m8.533.133 5.25 1.68A1.75 1.75 0 0 1 15 3.48V7c0 1.566-.32 3.182-1.303 4.682-.983 1.498-2.585 2.813-5.032 3.855a1.697 1.697 0 0 1-1.33 0c-2.447-1.042-4.049-2.357-5.032-3.855C1.32 10.182 1 8.566 1 7V3.48a1.75 1.75 0 0 1 1.217-1.667l5.25-1.68a1.748 1.748 0 0 1 1.066 0Zm-.61 1.429.001.001-5.25 1.68a.251.251 0 0 0-.174.237V7c0 1.36.275 2.666 1.057 3.859.784 1.194 2.121 2.342 4.366 3.298a.196.196 0 0 0 .154 0c2.245-.957 3.582-2.103 4.366-3.297C13.225 9.666 13.5 8.358 13.5 7V3.48a.25.25 0 0 0-.174-.238l-5.25-1.68a.25.25 0 0 0-.153 0ZM11.28 6.28l-3.5 3.5a.75.75 0 0 1-1.06 0l-1.5-1.5a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l.97.97 2.97-2.97a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"></path></svg>GitHub Advanced Security</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Enterprise-grade security features</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/features/copilot/copilot-business" data-analytics-event="{&quot;action&quot;:&quot;copilot_for_business&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;enterprise&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;copilot_for_business_link_enterprise_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-copilot NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M7.998 15.035c-4.562 0-7.873-2.914-7.998-3.749V9.338c.085-.628.677-1.686 1.588-2.065.013-.07.024-.143.036-.218.029-.183.06-.384.126-.612-.201-.508-.254-1.084-.254-1.656 0-.87.128-1.769.693-2.484.579-.733 1.494-1.124 2.724-1.261 1.206-.134 2.262.034 2.944.765.05.053.096.108.139.165.044-.057.094-.112.143-.165.682-.731 1.738-.899 2.944-.765 1.23.137 2.145.528 2.724 1.261.566.715.693 1.614.693 2.484 0 .572-.053 1.148-.254 1.656.066.228.098.429.126.612.012.076.024.148.037.218.924.385 1.522 1.471 1.591 2.095v1.872c0 .766-3.351 3.795-8.002 3.795Zm0-1.485c2.28 0 4.584-1.11 5.002-1.433V7.862l-.023-.116c-.49.21-1.075.291-1.727.291-1.146 0-2.059-.327-2.71-.991A3.222 3.222 0 0 1 8 6.303a3.24 3.24 0 0 1-.544.743c-.65.664-1.563.991-2.71.991-.652 0-1.236-.081-1.727-.291l-.023.116v4.255c.419.323 2.722 1.433 5.002 1.433ZM6.762 2.83c-.193-.206-.637-.413-1.682-.297-1.019.113-1.479.404-1.713.7-.247.312-.369.789-.369 1.554 0 .793.129 1.171.308 1.371.162.181.519.379 1.442.379.853 0 1.339-.235 1.638-.54.315-.322.527-.827.617-1.553.117-.935-.037-1.395-.241-1.614Zm4.155-.297c-1.044-.116-1.488.091-1.681.297-.204.219-.359.679-.242 1.614.091.726.303 1.231.618 1.553.299.305.784.54 1.638.54.922 0 1.28-.198 1.442-.379.179-.2.308-.578.308-1.371 0-.765-.123-1.242-.37-1.554-.233-.296-.693-.587-1.713-.7Z"></path><path d="M6.25 9.037a.75.75 0 0 1 .75.75v1.501a.75.75 0 0 1-1.5 0V9.787a.75.75 0 0 1 .75-.75Zm4.25.75v1.501a.75.75 0 0 1-1.5 0V9.787a.75.75 0 0 1 1.5 0Z"></path></svg>Copilot for Business</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Enterprise-grade AI features</span></span></a></li><li><a class="Primer_Brand__Link-module__Link___lF11y Primer_Brand__Link-module__Link--default___VRVW0 NavLink-module__link__EG3d4" href="https://github.com/enterprise/premium-support" data-analytics-event="{&quot;action&quot;:&quot;premium_support&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;enterprise&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;premium_support_link_enterprise_navbar&quot;}"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Link-module__Link--label___jM8Ty"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS Primer_Brand__Text-module__Text--weight-medium___qJKf_ NavLink-module__title__Q7t0p"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-comment-discussion NavLink-module__icon__ltGNM" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M1.75 1h8.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 10.25 10H7.061l-2.574 2.573A1.458 1.458 0 0 1 2 11.543V10h-.25A1.75 1.75 0 0 1 0 8.25v-5.5C0 1.784.784 1 1.75 1ZM1.5 2.75v5.5c0 .138.112.25.25.25h1a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h3.5a.25.25 0 0 0 .25-.25v-5.5a.25.25 0 0 0-.25-.25h-8.5a.25.25 0 0 0-.25.25Zm13 2a.25.25 0 0 0-.25-.25h-.5a.75.75 0 0 1 0-1.5h.5c.966 0 1.75.784 1.75 1.75v5.5A1.75 1.75 0 0 1 14.25 12H14v1.543a1.458 1.458 0 0 1-2.487 1.03L9.22 12.28a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215l2.22 2.22v-2.19a.75.75 0 0 1 .75-.75h1a.25.25 0 0 0 .25-.25Z"></path></svg>Premium Support</span><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--muted___rE6mh Primer_Brand__Text-module__Text--200____P1wy Primer_Brand__Text-module__Text--antialiased___TYoXS NavLink-module__subtitle__X4gkW">Enterprise-grade 24/7 support</span></span></a></li></ul></div></li></ul></div></div></li><li><a href="https://github.com/pricing" data-analytics-event="{&quot;action&quot;:&quot;pricing&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;pricing&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;pricing_link_pricing_navbar&quot;}" class="MarketingNavigation-module__navLink__hUomM">Pricing</a></li></ul></nav><div class="MarketingHeader-module__ctaContainer__tBmPz"><div class="HeaderSearch-module__searchSlot__oVOUS"><button class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--subtle___F7pEE Primer_Brand__Button-module__Button--size-small___zQrEw HeaderSearch-module__trigger__zsF9q" type="button" aria-haspopup="dialog" aria-expanded="false" aria-label="Search or jump to, type / to search" data-analytics-event="{&quot;action&quot;:&quot;searchbar&quot;,&quot;tag&quot;:&quot;input&quot;,&quot;context&quot;:&quot;global&quot;,&quot;location&quot;:&quot;navbar&quot;,&quot;label&quot;:&quot;searchbar_input_global_navbar&quot;}"><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-subtle___8ndWH"><span class="HeaderSearch-module__content__kMpxU"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-search HeaderSearch-module__icon__wcrHX" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215ZM11.5 7a4.499 4.499 0 1 0-8.997 0A4.499 4.499 0 0 0 11.5 7Z"></path></svg><span class="HeaderSearch-module__label__d1iWG">Search</span><kbd class="HeaderSearch-module__kbd__HNG0o" aria-hidden="true">/</kbd></span></span></span></button><div class="d-none"></div></div><div class="AuthCTAs-module__signInWrap__q2P60"><a class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--subtle___F7pEE Primer_Brand__Button-module__Button--size-small___zQrEw AuthCTAs-module__cta__WpwQq AuthCTAs-module__desktopActionGap__UZuXT AuthCTAs-module__hiddenBelowLg__BfKBw" href="/login?return_to=https%3A%2F%2Fgithub.com%2Fgleb7499%2Fscada-mobile%2Fblob%2Fmain%2FAUTH_FLOW.md" data-analytics-event="{&quot;action&quot;:&quot;sign_in&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;auth_cta&quot;,&quot;location&quot;:&quot;header&quot;,&quot;label&quot;:&quot;sign_in_link_auth_cta_header&quot;}" data-hydro-click="{&quot;event_type&quot;:&quot;authentication.click&quot;,&quot;payload&quot;:{&quot;location_in_page&quot;:&quot;site header menu&quot;,&quot;repository_id&quot;:null,&quot;auth_type&quot;:&quot;SIGN_UP&quot;,&quot;originating_url&quot;:&quot;https://github.com/gleb7499/scada-mobile/blob/main/AUTH_FLOW.md&quot;,&quot;user_id&quot;:null}}" data-hydro-click-hmac="ce3b534c9b44bf8ccaf1aca02d708edb1f848d66a53595ca6bda592b01e43d83"><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-subtle___8ndWH">Sign in</span></span></a></div><a class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--secondary___gHnw_ Primer_Brand__Button-module__Button--size-small___zQrEw AuthCTAs-module__cta__WpwQq" href="/signup?ref_cta=Sign+up&amp;ref_loc=header+logged+out&amp;ref_page=%2F%3Cuser-name%3E%2F%3Crepo-name%3E%2Fblob%2Fshow&amp;source=header-repo&amp;source_repo=gleb7499%2Fscada-mobile" data-analytics-event="{&quot;action&quot;:&quot;sign_up&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;context&quot;:&quot;auth_cta&quot;,&quot;location&quot;:&quot;header&quot;,&quot;label&quot;:&quot;sign_up_link_auth_cta_header&quot;}" data-hydro-click="{&quot;event_type&quot;:&quot;authentication.click&quot;,&quot;payload&quot;:{&quot;location_in_page&quot;:&quot;site header menu&quot;,&quot;repository_id&quot;:null,&quot;auth_type&quot;:&quot;SIGN_UP&quot;,&quot;originating_url&quot;:&quot;https://github.com/gleb7499/scada-mobile/blob/main/AUTH_FLOW.md&quot;,&quot;user_id&quot;:null}}" data-hydro-click-hmac="ce3b534c9b44bf8ccaf1aca02d708edb1f848d66a53595ca6bda592b01e43d83"><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-secondary___eJ0_a">Sign up</span></span></a><button class="Primer_Brand__Button-module__Button___scH9Z Primer_Brand__Button-module__Button--subtle___F7pEE Primer_Brand__Button-module__Button--size-small___zQrEw HeaderAppearanceSettings-module__trigger__hUheK" type="button" aria-haspopup="dialog" aria-labelledby="_R_fbd_"><span class="Primer_Brand__Button-module__Button__leading-visual___jjtTe" data-testid="Button-leading-visual"><svg data-component="Octicon" focusable="false" aria-hidden="true" class="octicon octicon-sliders Primer_Brand__Button-module__Button__icon-visual____qybb" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M15 2.75a.75.75 0 0 1-.75.75h-4a.75.75 0 0 1 0-1.5h4a.75.75 0 0 1 .75.75Zm-8.5.75v1.25a.75.75 0 0 0 1.5 0v-4a.75.75 0 0 0-1.5 0V2H1.75a.75.75 0 0 0 0 1.5H6.5Zm1.25 5.25a.75.75 0 0 0 0-1.5h-6a.75.75 0 0 0 0 1.5h6ZM15 8a.75.75 0 0 1-.75.75H11.5V10a.75.75 0 1 1-1.5 0V6a.75.75 0 0 1 1.5 0v1.25h2.75A.75.75 0 0 1 15 8Zm-9 5.25v-2a.75.75 0 0 0-1.5 0v1.25H1.75a.75.75 0 0 0 0 1.5H4.5v1.25a.75.75 0 0 0 1.5 0v-2Zm9 0a.75.75 0 0 1-.75.75h-6a.75.75 0 0 1 0-1.5h6a.75.75 0 0 1 .75.75Z"></path></svg></span><span class="Primer_Brand__Button-module__Button__text___ED0bX"><span class="Primer_Brand__Text-module__Text___XeGJJ Primer_Brand__Text-module__Text-font--mona-sans___a8XJD Primer_Brand__Text-module__Text--default___GhPh_ Primer_Brand__Text-module__Text--100___B2ueX Primer_Brand__Text-module__Text--weight-medium___qJKf_ Primer_Brand__Button-module__Button--label___qrkyz Primer_Brand__Button-module__Button--label-subtle___8ndWH"></span></span></button><div class="Primer_Brand__Tooltip-module__Tooltip___0Eipx" data-direction="s" aria-hidden="true" id="_R_fbd_">Appearance settings</div></div></div></div></div><div class="MarketingHeader-module__bottomBorder__uZT38" aria-hidden="true"></div></header></div></div>
</react-partial>



      <div hidden="hidden" data-view-component="true" class="js-stale-session-flash stale-session-flash flash flash-warn flash-full">
  
        <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
    <path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
        <span class="js-stale-session-flash-signed-in" hidden>You signed in with another tab or window. <a class="Link--inTextBlock" href="">Reload</a> to refresh your session.</span>
        <span class="js-stale-session-flash-signed-out" hidden>You signed out in another tab or window. <a class="Link--inTextBlock" href="">Reload</a> to refresh your session.</span>
        <span class="js-stale-session-flash-switched" hidden>You switched accounts on another tab or window. <a class="Link--inTextBlock" href="">Reload</a> to refresh your session.</span>

    <button id="icon-button-7625a313-9805-4aa3-ab31-5319002ddbf5" aria-labelledby="tooltip-4a8b3446-7c00-49b8-9e22-271d70be8779" type="button" data-view-component="true" class="Button Button--iconOnly Button--invisible Button--medium flash-close js-flash-close">  <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-x Button-visual">
    <path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path>
</svg>
</button><tool-tip id="tooltip-4a8b3446-7c00-49b8-9e22-271d70be8779" for="icon-button-7625a313-9805-4aa3-ab31-5319002ddbf5" popover="manual" data-direction="s" data-type="label" data-view-component="true" class="sr-only position-absolute">Dismiss alert</tool-tip>


  
</div>
    </div>

  <div id="start-of-content" class="show-on-focus"></div>








    <div id="js-flash-container" class="flash-container" data-turbo-replace>






  <template class="js-flash-template">
    
<div class="flash flash-full   {{ className }}">
  <div >
    <button autofocus class="flash-close js-flash-close" type="button" aria-label="Dismiss this message">
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-x">
    <path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path>
</svg>
    </button>
    <div aria-atomic="true" role="alert" class="js-flash-alert">
      
      <div>{{ message }}</div>

    </div>
  </div>
</div>
  </template>
</div>


    






  <div
    class="application-main "
    data-commit-hovercards-enabled
    data-discussion-hovercards-enabled
    data-issue-and-pr-hovercards-enabled
    data-project-hovercards-enabled
  >
        <div itemscope itemtype="http://schema.org/SoftwareSourceCode" class="">
    <main id="js-repo-pjax-container" >
      
      








  

    <link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ym-6bc2c9bf8112c038.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/sk2-8deeef9351be3911.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/ni-3e194d222c7770bf.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/7x-71d30cb240cd901f.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/wwk-ad869086822937b1.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/a1-9f79841ecac1da8e.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/wzg-40a3213b3e83400b.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/5f-64f8e7d5bcbd4787.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/z5-b53a93078d026f1e.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/8h-cef26f68c1331011.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/fq-3ce13a77e863807a.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/a7-596b507bd20acddc.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/xfx-c82d8352413aea0a.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/j4-b3a9e87fa7878a98.js" fetchpriority="low" />
<link crossorigin="anonymous" rel="modulepreload" href="https://github.githubassets.com/assets/global-nav-bar-29c7aef68ee9db41.js" fetchpriority="low" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/primer-react-css.2d881b4d4a503d1c.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/pj.75627bfeb9ceb1da.module.css" />
<link crossorigin="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/global-nav-bar.2f2560cba06ab359.module.css" />

<react-partial
  partial-name="global-nav-bar"
  data-ssr="true"
  data-attempted-ssr="true"
  data-react-profiling="false"
>
  
  <script type="application/json" data-target="react-partial.embeddedData">{"props":{"contextRegion":{"crumbs":[{"crumb_type":"user","label":"gleb7499","is_root":false,"href":"/gleb7499"},{"crumb_type":"repository","label":"scada-mobile","is_root":false,"octicon":"repo-forked","href":"/gleb7499/scada-mobile"}],"localNavigation":[{"id":"code","icon":"code","label":"Code","href":"/gleb7499/scada-mobile","selectedLinks":["repo_source","repo_downloads","repo_commits","repo_releases","repo_tags","repo_branches","repo_packages","repo_deployments","repo_attestations"],"popoverTarget":false,"commandId":"repositories:go-to-code","reactNav":{"appTarget":"code-view","anchor":"code-view-repo-link"},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"pull-requests","icon":"git-pull-request","label":"Pull requests","href":"/gleb7499/scada-mobile/pulls","selectedLinks":["repo_pulls","checks"],"count":0,"popoverTarget":false,"commandId":"repositories:go-to-pull-requests","reactNav":{"appTarget":null,"anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"actions","icon":"play","label":"Actions","href":"/gleb7499/scada-mobile/actions","selectedLinks":["repo_actions"],"popoverTarget":false,"commandId":"repositories:go-to-actions","reactNav":{"appTarget":"actions-workflows","anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"projects","icon":"table","label":"Projects","href":"/gleb7499/scada-mobile/projects","selectedLinks":["repo_projects","new_repo_project","repo_project"],"popoverTarget":false,"commandId":"repositories:go-to-projects","reactNav":{"appTarget":"repo","anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"security-and-quality","icon":"shield","label":"Security and quality","href":"/gleb7499/scada-mobile/security","selectedLinks":["security","overview","alerts","policy","token_scanning","code_scanning"],"count":0,"popoverTarget":false,"commandId":"repositories:go-to-security","reactNav":{"appTarget":null,"anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}},{"id":"insights","icon":"graph","label":"Insights","href":"/gleb7499/scada-mobile/pulse","selectedLinks":["repo_graphs","repo_contributors","dependency_graph","dependabot_updates","pulse","people","community"],"popoverTarget":false,"commandId":"repositories:go-to-insights","reactNav":{"appTarget":null,"anchor":null},"turboNav":{"frame":"repo-content-turbo-frame"}}],"localNavigationUpdateChannel":null,"selectedLink":"repo_source"},"owner":null,"headerLogo":{"href":"/","aria-label":"Homepage "},"notifications":{"indicatorMode":"disabled","websocketChannel":null,"fetchIndicatorSrc":"/notifications/indicator","fetchIndicatorEnabled":false},"issues":{"href":"/issues"},"pulls":{"href":"/pulls"},"contributedRepos":{"href":"/repos"},"copilot":{"show":false,"showAgentsButton":false,"showRelaunchAnnouncement":false,"copilotChatUrl":null,"copilotApiUrl":"https://api.githubcopilot.com"},"search":{"show":true,"showCommandPalette":false,"isSearchPage":false,"isJumpToSearch":false,"searchContext":{"scope":"repo:gleb7499/scada-mobile","current_repo_name":"scada-mobile","current_repo_nwo":"gleb7499/scada-mobile","user_id":"gleb7499"}},"commandPalette":null,"enterpriseBar":{"show":false},"globalTransactionalMessage":[],"payloadsUrl":"/_global-navigation/payloads.json?can_toggle_site_admin_and_employee_status=0\u0026is_admin_mode_on=0\u0026is_ui_opted_out=0\u0026show_ui_opt_out=0\u0026v=7","contextRegionUrl":"/_global-navigation/context-region.json"}}</script>
  <div data-target="react-partial.reactRoot"><header aria-label="Global navigation menu" data-component="Stack" class="GlobalNav styles-module__appHeader__YzYWk prc-Stack-Stack-UQ9k6" data-gap="none" data-direction="vertical" data-align="stretch" data-wrap="nowrap" data-justify="start" data-padding="none"><div data-component="Stack" class="prc-Stack-Stack-UQ9k6" data-direction="horizontal" data-align="center" data-wrap="nowrap" data-justify="center" data-padding="none"><div data-testid="top-nav-center" data-component="Stack" class="styles-module__center__R3QRv styles-module__withLocalNavigation__rjTJ_ GlobalNavBar-module__loggedOut__Jv1rk prc-Stack-Stack-UQ9k6" data-gap="condensed" data-direction="horizontal" data-align="stretch" data-wrap="nowrap" data-justify="start" data-padding="normal"><nav class="styles-module__contextRegion__VbSp2 prc-Breadcrumbs-BreadcrumbsBase-3Gb-B" aria-label="Breadcrumbs" data-overflow="menu" data-variant="normal" data-component="Breadcrumbs"><ol class="prc-Breadcrumbs-BreadcrumbsList-BKjpe"><li class="prc-Breadcrumbs-ItemWrapper-k0NLn"><a class="styles-module__contextCrumb__IzGIq prc-Breadcrumbs-Item-jcraJ" data-component="Breadcrumbs.Item" href="/gleb7499" data-discover="true"><span class="">gleb7499</span></a></li><li class="prc-Breadcrumbs-ItemWrapper-k0NLn"><a class="styles-module__contextCrumb__IzGIq prc-Breadcrumbs-Item-jcraJ" data-component="Breadcrumbs.Item" href="/gleb7499/scada-mobile" data-discover="true"><span class="styles-module__contextCrumbLast__tI2e3">scada-mobile</span><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-repo-forked styles-module__trailingIcon__sCKDh" viewBox="0 0 16 16" width="12" height="12" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M5 5.372v.878c0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75v-.878a2.25 2.25 0 1 1 1.5 0v.878a2.25 2.25 0 0 1-2.25 2.25h-1.5v2.128a2.251 2.251 0 1 1-1.5 0V8.5h-1.5A2.25 2.25 0 0 1 3.5 6.25v-.878a2.25 2.25 0 1 1 1.5 0ZM5 3.25a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Zm6.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm-3 8.75a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Z"></path></svg></a></li></ol></nav></div><div data-testid="top-nav-right" data-component="Stack" class="styles-module__right__mlBQg styles-module__withLocalNavigation__rjTJ_ styles-module__rightWithResponsiveCreateButton__SKn2W prc-Stack-Stack-UQ9k6" data-gap="condensed" data-direction="horizontal" data-align="center" data-wrap="nowrap" data-justify="start" data-padding="normal"></div></div><h2 class="prc-src-InternalVisuallyHidden-2YaI6">Repository navigation</h2><nav class="prc-components-UnderlineWrapper-eT-Yj prc-UnderlineNav-UnderlineWrapper-GWONT LocalNavigation-module__LocalNavigation__b0Xc0" aria-label="Repository" data-variant="inset" data-overflow-mode="wrap" data-hide-icons-breakpoint="medium"><ul class="prc-UnderlineNav-ItemsList-oj8gN prc-components-UnderlineItemList-xKlKC" role="list"><li role="presentation" aria-hidden="true" class="prc-UnderlineNav-WrapSpacer--aLgz"></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a aria-current="page" data-tab-item="code" data-react-nav="code-view" data-react-nav-anchor="code-view-repo-link" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/gleb7499/scada-mobile" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-code" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m11.28 3.22 4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.749.749 0 0 1-1.275-.326.749.749 0 0 1 .215-.734L13.94 8l-3.72-3.72a.749.749 0 0 1 .326-1.275.749.749 0 0 1 .734.215Zm-6.56 0a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042L2.06 8l3.72 3.72a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L.47 8.53a.75.75 0 0 1 0-1.06Z"></path></svg></span><span data-component="text" data-content="Code">Code</span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="pull-requests" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/gleb7499/scada-mobile/pulls" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-git-pull-request" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M1.5 3.25a2.25 2.25 0 1 1 3 2.122v5.256a2.251 2.251 0 1 1-1.5 0V5.372A2.25 2.25 0 0 1 1.5 3.25Zm5.677-.177L9.573.677A.25.25 0 0 1 10 .854V2.5h1A2.5 2.5 0 0 1 13.5 5v5.628a2.251 2.251 0 1 1-1.5 0V5a1 1 0 0 0-1-1h-1v1.646a.25.25 0 0 1-.427.177L7.177 3.427a.25.25 0 0 1 0-.354ZM3.75 2.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm0 9.5a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Zm8.25.75a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Z"></path></svg></span><span data-component="text" data-content="Pull requests">Pull requests</span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="actions" data-react-nav="actions-workflows" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/gleb7499/scada-mobile/actions" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-play" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0ZM1.5 8a6.5 6.5 0 1 0 13 0 6.5 6.5 0 0 0-13 0Zm4.879-2.773 4.264 2.559a.25.25 0 0 1 0 .428l-4.264 2.559A.25.25 0 0 1 6 10.559V5.442a.25.25 0 0 1 .379-.215Z"></path></svg></span><span data-component="text" data-content="Actions">Actions</span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="projects" data-react-nav="repo" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/gleb7499/scada-mobile/projects" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-table" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25ZM6.5 6.5v8h7.75a.25.25 0 0 0 .25-.25V6.5Zm8-1.5V1.75a.25.25 0 0 0-.25-.25H6.5V5Zm-13 1.5v7.75c0 .138.112.25.25.25H5v-8ZM5 5V1.5H1.75a.25.25 0 0 0-.25.25V5Z"></path></svg></span><span data-component="text" data-content="Projects">Projects</span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="security-and-quality" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/gleb7499/scada-mobile/security" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-shield" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M7.467.133a1.748 1.748 0 0 1 1.066 0l5.25 1.68A1.75 1.75 0 0 1 15 3.48V7c0 1.566-.32 3.182-1.303 4.682-.983 1.498-2.585 2.813-5.032 3.855a1.697 1.697 0 0 1-1.33 0c-2.447-1.042-4.049-2.357-5.032-3.855C1.32 10.182 1 8.566 1 7V3.48a1.75 1.75 0 0 1 1.217-1.667Zm.61 1.429a.25.25 0 0 0-.153 0l-5.25 1.68a.25.25 0 0 0-.174.238V7c0 1.358.275 2.666 1.057 3.86.784 1.194 2.121 2.34 4.366 3.297a.196.196 0 0 0 .154 0c2.245-.956 3.582-2.104 4.366-3.298C13.225 9.666 13.5 8.36 13.5 7V3.48a.251.251 0 0 0-.174-.237l-5.25-1.68ZM8.75 4.75v3a.75.75 0 0 1-1.5 0v-3a.75.75 0 0 1 1.5 0ZM9 10.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path></svg></span><span data-component="text" data-content="Security and quality">Security and quality</span></a></li><li class="prc-UnderlineNav-UnderlineNavItem-syRjR"><a data-tab-item="insights" data-turbo-frame="repo-content-turbo-frame" class="prc-components-UnderlineItem-7fP-n" href="/gleb7499/scada-mobile/pulse" data-discover="true"><span data-component="icon"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-graph" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M1.5 1.75V13.5h13.75a.75.75 0 0 1 0 1.5H.75a.75.75 0 0 1-.75-.75V1.75a.75.75 0 0 1 1.5 0Zm14.28 2.53-5.25 5.25a.75.75 0 0 1-1.06 0L7 7.06 4.28 9.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.25-3.25a.75.75 0 0 1 1.06 0L10 7.94l4.72-4.72a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042Z"></path></svg></span><span data-component="text" data-content="Insights">Insights</span></a></li></ul><div class="prc-UnderlineNav-MoreButtonContainer-Dnrq6"><div class="prc-UnderlineNav-MoreButtonDivider-dN0a-"></div><button data-component="overflow-menu-button" type="button" aria-haspopup="true" aria-expanded="false" tabindex="0" class="prc-Button-ButtonBase-9n-Xk prc-UnderlineNav-MoreButton-Y8soj" data-loading="false" data-size="medium" data-variant="invisible" id="_R_2ktp_"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="text" class="prc-Button-Label-FWkx3"><span>More<span class="prc-src-InternalVisuallyHidden-2YaI6"> items</span></span></span></span><span data-component="trailingAction" class="prc-Button-Visual-YNt2F prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-down" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m4.427 7.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.396 7H4.604a.25.25 0 0 0-.177.427Z"></path></svg></span></button></div></nav><div class="d-none"></div></header></div>
</react-partial>


  



<turbo-frame id="repo-content-turbo-frame" target="_top" data-turbo-action="advance" class="">
    <div id="repo-content-pjax-container" class="repository-content " >
    



    
      
    








<react-app
  app-name="code-view"
  initial-path="/gleb7499/scada-mobile/blob/main/AUTH_FLOW.md"
  style="display: block; min-height: calc(100vh - 64px);"
  data-attempted-ssr="true"
  data-ssr="true"
  data-lazy="false"
  data-alternate="false"
  data-data-router-enabled="true"
  data-react-profiling="false"
>
  
  <script type="application/json" data-target="react-app.embeddedData">{"payload":{"codeViewBlobRoute":{"csv":null,"csvError":null,"headerInfo":{"toc":[{"level":1,"text":"Поток аутентификации (SCADA Mobile)","anchor":"поток-аутентификации-scada-mobile","htmlText":"Поток аутентификации (SCADA Mobile)"},{"level":2,"text":"Purpose","anchor":"purpose","htmlText":"Purpose"},{"level":2,"text":"Table of contents","anchor":"table-of-contents","htmlText":"Table of contents"},{"level":2,"text":"Общая диаграмма","anchor":"общая-диаграмма","htmlText":"Общая диаграмма"},{"level":2,"text":"Этап 1: Вход (login)","anchor":"этап-1-вход-login","htmlText":"Этап 1: Вход (login)"},{"level":3,"text":"Поля ответа login","anchor":"поля-ответа-login","htmlText":"Поля ответа login"},{"level":2,"text":"Этап 2: Использование access token","anchor":"этап-2-использование-access-token","htmlText":"Этап 2: Использование access token"},{"level":2,"text":"Этап 3: Ротация токенов (refresh)","anchor":"этап-3-ротация-токенов-refresh","htmlText":"Этап 3: Ротация токенов (refresh)"},{"level":3,"text":"Почему ротация важна","anchor":"почему-ротация-важна","htmlText":"Почему ротация важна"},{"level":2,"text":"Этап 4: Выход (logout)","anchor":"этап-4-выход-logout","htmlText":"Этап 4: Выход (logout)"},{"level":2,"text":"Этап 5: WebSocket аутентификация","anchor":"этап-5-websocket-аутентификация","htmlText":"Этап 5: WebSocket аутентификация"},{"level":2,"text":"Этап 6: Доступ к админ-панели","anchor":"этап-6-доступ-к-админ-панели","htmlText":"Этап 6: Доступ к админ-панели"},{"level":2,"text":"Сравнение токенов","anchor":"сравнение-токенов","htmlText":"Сравнение токенов"}]},"issueTemplate":null,"discussionTemplate":null,"richText":"\u003carticle class=\"markdown-body entry-content container-lg\" itemprop=\"text\"\u003e\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch1 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eПоток аутентификации (SCADA Mobile)\u003c/h1\u003e\u003ca id=\"user-content-поток-аутентификации-scada-mobile\" class=\"anchor\" aria-label=\"Permalink: Поток аутентификации (SCADA Mobile)\" href=\"#поток-аутентификации-scada-mobile\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003ePurpose\u003c/h2\u003e\u003ca id=\"user-content-purpose\" class=\"anchor\" aria-label=\"Permalink: Purpose\" href=\"#purpose\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003eДокумент описывает полный поток аутентификации и авторизации: от входа пользователя до WebSocket-соединения.\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eTable of contents\u003c/h2\u003e\u003ca id=\"user-content-table-of-contents\" class=\"anchor\" aria-label=\"Permalink: Table of contents\" href=\"#table-of-contents\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003ca href=\"#purpose\"\u003ePurpose\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#%D0%BE%D0%B1%D1%89%D0%B0%D1%8F-%D0%B4%D0%B8%D0%B0%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B0\"\u003eОбщая диаграмма\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#%D1%8D%D1%82%D0%B0%D0%BF-1-%D0%B2%D1%85%D0%BE%D0%B4-login\"\u003eЭтап 1: Вход (login)\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#%D1%8D%D1%82%D0%B0%D0%BF-2-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-access-token\"\u003eЭтап 2: Использование access token\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#%D1%8D%D1%82%D0%B0%D0%BF-3-%D1%80%D0%BE%D1%82%D0%B0%D1%86%D0%B8%D1%8F-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%BE%D0%B2-refresh\"\u003eЭтап 3: Ротация токенов (refresh)\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#%D1%8D%D1%82%D0%B0%D0%BF-4-%D0%B2%D1%8B%D1%85%D0%BE%D0%B4-logout\"\u003eЭтап 4: Выход (logout)\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#%D1%8D%D1%82%D0%B0%D0%BF-5-websocket-%D0%B0%D1%83%D1%82%D0%B5%D0%BD%D1%82%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%86%D0%B8%D1%8F\"\u003eЭтап 5: WebSocket аутентификация\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#%D1%8D%D1%82%D0%B0%D0%BF-6-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF-%D0%BA-%D0%B0%D0%B4%D0%BC%D0%B8%D0%BD-%D0%BF%D0%B0%D0%BD%D0%B5%D0%BB%D0%B8\"\u003eЭтап 6: Доступ к админ-панели\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#%D1%81%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%BE%D0%B2\"\u003eСравнение токенов\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eОбщая диаграмма\u003c/h2\u003e\u003ca id=\"user-content-общая-диаграмма\" class=\"anchor\" aria-label=\"Permalink: Общая диаграмма\" href=\"#общая-диаграмма\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003csection class=\"js-render-needs-enrichment render-needs-enrichment position-relative\" data-identity=\"d926c65a-56b5-4ab1-82a6-7244d7d1bce4\" data-host=\"https://viewscreen.githubusercontent.com\" data-src=\"https://viewscreen.githubusercontent.com/markdown/mermaid?docs_host=https%3A%2F%2Fdocs.github.com\" data-type=\"mermaid\" aria-label=\"mermaid rendered output container\"\u003e\n  \u003cdiv class=\"js-render-enrichment-target\" data-json=\"{\u0026quot;data\u0026quot;:\u0026quot;flowchart TB\\n    subgraph Client[\\\u0026quot;Клиент (Frontend)\\\u0026quot;]\\n        Login[\\\u0026quot;Экран входа\\\u0026quot;]\\n        Storage[\\\u0026quot;localStorage\\\u0026quot;]\\n        ApiClient[\\\u0026quot;API Client\\\u0026quot;]\\n        WsClient[\\\u0026quot;WebSocket Client\\\u0026quot;]\\n    end\\n\\n    subgraph Backend[\\\u0026quot;Backend (Spring Security)\\\u0026quot;]\\n        AuthController[\\\u0026quot;AuthController\\\u0026quot;]\\n        AuthService[\\\u0026quot;AuthService\\\u0026quot;]\\n        JwtProvider[\\\u0026quot;JwtTokenProvider\\\u0026quot;]\\n        JwtDecoder[\\\u0026quot;JwtDecoder\\\u0026quot;]\\n        UserRepo[\\\u0026quot;UserAuthRepository\\\u0026quot;]\\n        TokenRepo[\\\u0026quot;RefreshTokenRepository\\\u0026quot;]\\n        SecurityFilter[\\\u0026quot;Security Filter Chain\\\u0026quot;]\\n        WsInterceptor[\\\u0026quot;WebSocketJwtInterceptor\\\u0026quot;]\\n    end\\n\\n    subgraph DB[\\\u0026quot;БД (PostgreSQL)\\\u0026quot;]\\n        Users[\\\u0026quot;users\\\u0026quot;]\\n        RefreshTokens[\\\u0026quot;refresh_tokens\\\u0026quot;]\\n    end\\n\\n    Login --\u0026gt;|\\\u0026quot;POST /auth/login\u0026lt;br/\u0026gt;{workerCode, password}\\\u0026quot;| AuthController\\n    AuthController --\u0026gt; AuthService\\n    AuthService --\u0026gt; UserRepo\\n    UserRepo --\u0026gt; Users\\n    AuthService --\u0026gt; JwtProvider\\n    JwtProvider --\u0026gt;|\\\u0026quot;accessToken (JWT)\\\u0026quot;| AuthController\\n    JwtProvider --\u0026gt;|\\\u0026quot;refreshToken (UUID)\\\u0026quot;| AuthService\\n    AuthService --\u0026gt; TokenRepo\\n    TokenRepo --\u0026gt; RefreshTokens\\n    AuthController --\u0026gt;|\\\u0026quot;{accessToken, refreshToken}\\\u0026quot;| Storage\\n\\n    Storage --\u0026gt;|\\\u0026quot;Authorization: Bearer \u0026lt;accessToken\u0026gt;\\\u0026quot;| ApiClient\\n    ApiClient --\u0026gt;|\\\u0026quot;Запрос к API\\\u0026quot;| SecurityFilter\\n    SecurityFilter --\u0026gt; JwtDecoder\\n    JwtDecoder --\u0026gt;|\\\u0026quot;Валидация подписи,\u0026lt;br/\u0026gt;issuer, audience, expiry\\\u0026quot;| SecurityFilter\\n\\n    Storage --\u0026gt;|\\\u0026quot;?token=\u0026lt;accessToken\u0026gt;\\\u0026quot;| WsClient\\n    WsClient --\u0026gt;|\\\u0026quot;WebSocket handshake\\\u0026quot;| WsInterceptor\\n    WsInterceptor --\u0026gt; JwtProvider\\n\\n    ApiClient --\u0026gt;|\\\u0026quot;POST /auth/refresh\u0026lt;br/\u0026gt;{refreshToken}\\\u0026quot;| AuthController\\n    AuthController --\u0026gt; AuthService\\n    AuthService --\u0026gt; TokenRepo\\n    TokenRepo --\u0026gt; RefreshTokens\\n    AuthService --\u0026gt;|\\\u0026quot;Новая пара токенов\\\u0026quot;| AuthController\\n    AuthController --\u0026gt;|\\\u0026quot;Обновить в Storage\\\u0026quot;| Storage\\n\u0026quot;}\" data-plain=\"flowchart TB\n    subgraph Client[\u0026quot;Клиент (Frontend)\u0026quot;]\n        Login[\u0026quot;Экран входа\u0026quot;]\n        Storage[\u0026quot;localStorage\u0026quot;]\n        ApiClient[\u0026quot;API Client\u0026quot;]\n        WsClient[\u0026quot;WebSocket Client\u0026quot;]\n    end\n\n    subgraph Backend[\u0026quot;Backend (Spring Security)\u0026quot;]\n        AuthController[\u0026quot;AuthController\u0026quot;]\n        AuthService[\u0026quot;AuthService\u0026quot;]\n        JwtProvider[\u0026quot;JwtTokenProvider\u0026quot;]\n        JwtDecoder[\u0026quot;JwtDecoder\u0026quot;]\n        UserRepo[\u0026quot;UserAuthRepository\u0026quot;]\n        TokenRepo[\u0026quot;RefreshTokenRepository\u0026quot;]\n        SecurityFilter[\u0026quot;Security Filter Chain\u0026quot;]\n        WsInterceptor[\u0026quot;WebSocketJwtInterceptor\u0026quot;]\n    end\n\n    subgraph DB[\u0026quot;БД (PostgreSQL)\u0026quot;]\n        Users[\u0026quot;users\u0026quot;]\n        RefreshTokens[\u0026quot;refresh_tokens\u0026quot;]\n    end\n\n    Login --\u0026gt;|\u0026quot;POST /auth/login\u0026lt;br/\u0026gt;{workerCode, password}\u0026quot;| AuthController\n    AuthController --\u0026gt; AuthService\n    AuthService --\u0026gt; UserRepo\n    UserRepo --\u0026gt; Users\n    AuthService --\u0026gt; JwtProvider\n    JwtProvider --\u0026gt;|\u0026quot;accessToken (JWT)\u0026quot;| AuthController\n    JwtProvider --\u0026gt;|\u0026quot;refreshToken (UUID)\u0026quot;| AuthService\n    AuthService --\u0026gt; TokenRepo\n    TokenRepo --\u0026gt; RefreshTokens\n    AuthController --\u0026gt;|\u0026quot;{accessToken, refreshToken}\u0026quot;| Storage\n\n    Storage --\u0026gt;|\u0026quot;Authorization: Bearer \u0026lt;accessToken\u0026gt;\u0026quot;| ApiClient\n    ApiClient --\u0026gt;|\u0026quot;Запрос к API\u0026quot;| SecurityFilter\n    SecurityFilter --\u0026gt; JwtDecoder\n    JwtDecoder --\u0026gt;|\u0026quot;Валидация подписи,\u0026lt;br/\u0026gt;issuer, audience, expiry\u0026quot;| SecurityFilter\n\n    Storage --\u0026gt;|\u0026quot;?token=\u0026lt;accessToken\u0026gt;\u0026quot;| WsClient\n    WsClient --\u0026gt;|\u0026quot;WebSocket handshake\u0026quot;| WsInterceptor\n    WsInterceptor --\u0026gt; JwtProvider\n\n    ApiClient --\u0026gt;|\u0026quot;POST /auth/refresh\u0026lt;br/\u0026gt;{refreshToken}\u0026quot;| AuthController\n    AuthController --\u0026gt; AuthService\n    AuthService --\u0026gt; TokenRepo\n    TokenRepo --\u0026gt; RefreshTokens\n    AuthService --\u0026gt;|\u0026quot;Новая пара токенов\u0026quot;| AuthController\n    AuthController --\u0026gt;|\u0026quot;Обновить в Storage\u0026quot;| Storage\n\" dir=\"auto\"\u003e\n    \u003cdiv class=\"render-plaintext-hidden\" dir=\"auto\"\u003e\n      \u003cpre lang=\"mermaid\" aria-label=\"Raw mermaid code\"\u003eflowchart TB\n    subgraph Client[\"Клиент (Frontend)\"]\n        Login[\"Экран входа\"]\n        Storage[\"localStorage\"]\n        ApiClient[\"API Client\"]\n        WsClient[\"WebSocket Client\"]\n    end\n\n    subgraph Backend[\"Backend (Spring Security)\"]\n        AuthController[\"AuthController\"]\n        AuthService[\"AuthService\"]\n        JwtProvider[\"JwtTokenProvider\"]\n        JwtDecoder[\"JwtDecoder\"]\n        UserRepo[\"UserAuthRepository\"]\n        TokenRepo[\"RefreshTokenRepository\"]\n        SecurityFilter[\"Security Filter Chain\"]\n        WsInterceptor[\"WebSocketJwtInterceptor\"]\n    end\n\n    subgraph DB[\"БД (PostgreSQL)\"]\n        Users[\"users\"]\n        RefreshTokens[\"refresh_tokens\"]\n    end\n\n    Login --\u0026gt;|\"POST /auth/login\u0026lt;br/\u0026gt;{workerCode, password}\"| AuthController\n    AuthController --\u0026gt; AuthService\n    AuthService --\u0026gt; UserRepo\n    UserRepo --\u0026gt; Users\n    AuthService --\u0026gt; JwtProvider\n    JwtProvider --\u0026gt;|\"accessToken (JWT)\"| AuthController\n    JwtProvider --\u0026gt;|\"refreshToken (UUID)\"| AuthService\n    AuthService --\u0026gt; TokenRepo\n    TokenRepo --\u0026gt; RefreshTokens\n    AuthController --\u0026gt;|\"{accessToken, refreshToken}\"| Storage\n\n    Storage --\u0026gt;|\"Authorization: Bearer \u0026lt;accessToken\u0026gt;\"| ApiClient\n    ApiClient --\u0026gt;|\"Запрос к API\"| SecurityFilter\n    SecurityFilter --\u0026gt; JwtDecoder\n    JwtDecoder --\u0026gt;|\"Валидация подписи,\u0026lt;br/\u0026gt;issuer, audience, expiry\"| SecurityFilter\n\n    Storage --\u0026gt;|\"?token=\u0026lt;accessToken\u0026gt;\"| WsClient\n    WsClient --\u0026gt;|\"WebSocket handshake\"| WsInterceptor\n    WsInterceptor --\u0026gt; JwtProvider\n\n    ApiClient --\u0026gt;|\"POST /auth/refresh\u0026lt;br/\u0026gt;{refreshToken}\"| AuthController\n    AuthController --\u0026gt; AuthService\n    AuthService --\u0026gt; TokenRepo\n    TokenRepo --\u0026gt; RefreshTokens\n    AuthService --\u0026gt;|\"Новая пара токенов\"| AuthController\n    AuthController --\u0026gt;|\"Обновить в Storage\"| Storage\n\u003c/pre\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cspan class=\"js-render-enrichment-loader d-flex flex-justify-center flex-items-center width-full\" style=\"min-height:100px\" role=\"presentation\"\u003e\n    \u003cspan data-view-component=\"true\"\u003e\n  \u003csvg style=\"box-sizing: content-box; color: var(--color-icon-primary);\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\" data-view-component=\"true\" class=\"octospinner mx-auto tmp-mx-auto anim-rotate\"\u003e\n    \u003ccircle cx=\"8\" cy=\"8\" r=\"7\" stroke=\"currentColor\" stroke-opacity=\"0.25\" stroke-width=\"2\" vector-effect=\"non-scaling-stroke\" fill=\"none\"\u003e\u003c/circle\u003e\n    \u003cpath d=\"M15 8a7.002 7.002 0 00-7-7\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" vector-effect=\"non-scaling-stroke\"\u003e\u003c/path\u003e\n\u003c/svg\u003e    \u003cspan class=\"sr-only\"\u003eLoading\u003c/span\u003e\n\u003c/span\u003e\n  \u003c/span\u003e\n\u003c/section\u003e\n\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eЭтап 1: Вход (login)\u003c/h2\u003e\u003ca id=\"user-content-этап-1-вход-login\" class=\"anchor\" aria-label=\"Permalink: Этап 1: Вход (login)\" href=\"#этап-1-вход-login\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003csection class=\"js-render-needs-enrichment render-needs-enrichment position-relative\" data-identity=\"cf26dd82-5506-41cf-9cac-aec21add3591\" data-host=\"https://viewscreen.githubusercontent.com\" data-src=\"https://viewscreen.githubusercontent.com/markdown/mermaid?docs_host=https%3A%2F%2Fdocs.github.com\" data-type=\"mermaid\" aria-label=\"mermaid rendered output container\"\u003e\n  \u003cdiv class=\"js-render-enrichment-target\" data-json=\"{\u0026quot;data\u0026quot;:\u0026quot;sequenceDiagram\\n    participant Client as Frontend\\n    participant Auth as AuthController\\n    participant Service as AuthService\\n    participant UserRepo as UserAuthJpaAdapter\\n    participant TokenRepo as RefreshTokenJpaAdapter\\n    participant DB as БД\\n\\n    Client-\u0026gt;\u0026gt;Auth: POST /auth/login\u0026lt;br/\u0026gt;{workerCode, password}\\n    Auth-\u0026gt;\u0026gt;Service: authenticate(workerCode, password)\\n    Service-\u0026gt;\u0026gt;UserRepo: findByCode(workerCode)\\n    UserRepo-\u0026gt;\u0026gt;DB: SELECT * FROM users WHERE code = ?\\n    DB--\u0026gt;\u0026gt;UserRepo: UserEntity\\n    UserRepo--\u0026gt;\u0026gt;Service: Optional\u0026lt;UserEntity\u0026gt;\\n\\n    alt Пользователь не найден\\n        Service--\u0026gt;\u0026gt;Auth: AuthenticationException\\n        Auth--\u0026gt;\u0026gt;Client: 401 Unauthorized\\n    else Пользователь найден\\n        Service-\u0026gt;\u0026gt;Service: BCrypt.checkpw(password, hash)\\n        alt Пароль неверный\\n            Service--\u0026gt;\u0026gt;Auth: AuthenticationException\\n            Auth--\u0026gt;\u0026gt;Client: 401 Unauthorized\\n        else Пароль верный\\n            Service-\u0026gt;\u0026gt;Service: generateAccessToken(userId, role)\\n            Note over Service: JWT HS256, 15 мин\u0026lt;br/\u0026gt;sub=userId, role=ROLE_*\\n            Service-\u0026gt;\u0026gt;Service: generateRefreshToken()\\n            Note over Service: UUID, 7 дней\\n            Service-\u0026gt;\u0026gt;Service: SHA-256(refreshToken)\\n            Service-\u0026gt;\u0026gt;TokenRepo: save(tokenHash, userId, expiresAt)\\n            TokenRepo-\u0026gt;\u0026gt;DB: INSERT INTO refresh_tokens\\n            TokenRepo--\u0026gt;\u0026gt;Service: RefreshTokenEntity\\n            Service--\u0026gt;\u0026gt;Auth: AuthLoginResponseDTO\\n            Auth--\u0026gt;\u0026gt;Client: 200 OK\u0026lt;br/\u0026gt;{accessToken, refreshToken, userId, code, fullName, role}\\n        end\\n    end\\n\u0026quot;}\" data-plain=\"sequenceDiagram\n    participant Client as Frontend\n    participant Auth as AuthController\n    participant Service as AuthService\n    participant UserRepo as UserAuthJpaAdapter\n    participant TokenRepo as RefreshTokenJpaAdapter\n    participant DB as БД\n\n    Client-\u0026gt;\u0026gt;Auth: POST /auth/login\u0026lt;br/\u0026gt;{workerCode, password}\n    Auth-\u0026gt;\u0026gt;Service: authenticate(workerCode, password)\n    Service-\u0026gt;\u0026gt;UserRepo: findByCode(workerCode)\n    UserRepo-\u0026gt;\u0026gt;DB: SELECT * FROM users WHERE code = ?\n    DB--\u0026gt;\u0026gt;UserRepo: UserEntity\n    UserRepo--\u0026gt;\u0026gt;Service: Optional\u0026lt;UserEntity\u0026gt;\n\n    alt Пользователь не найден\n        Service--\u0026gt;\u0026gt;Auth: AuthenticationException\n        Auth--\u0026gt;\u0026gt;Client: 401 Unauthorized\n    else Пользователь найден\n        Service-\u0026gt;\u0026gt;Service: BCrypt.checkpw(password, hash)\n        alt Пароль неверный\n            Service--\u0026gt;\u0026gt;Auth: AuthenticationException\n            Auth--\u0026gt;\u0026gt;Client: 401 Unauthorized\n        else Пароль верный\n            Service-\u0026gt;\u0026gt;Service: generateAccessToken(userId, role)\n            Note over Service: JWT HS256, 15 мин\u0026lt;br/\u0026gt;sub=userId, role=ROLE_*\n            Service-\u0026gt;\u0026gt;Service: generateRefreshToken()\n            Note over Service: UUID, 7 дней\n            Service-\u0026gt;\u0026gt;Service: SHA-256(refreshToken)\n            Service-\u0026gt;\u0026gt;TokenRepo: save(tokenHash, userId, expiresAt)\n            TokenRepo-\u0026gt;\u0026gt;DB: INSERT INTO refresh_tokens\n            TokenRepo--\u0026gt;\u0026gt;Service: RefreshTokenEntity\n            Service--\u0026gt;\u0026gt;Auth: AuthLoginResponseDTO\n            Auth--\u0026gt;\u0026gt;Client: 200 OK\u0026lt;br/\u0026gt;{accessToken, refreshToken, userId, code, fullName, role}\n        end\n    end\n\" dir=\"auto\"\u003e\n    \u003cdiv class=\"render-plaintext-hidden\" dir=\"auto\"\u003e\n      \u003cpre lang=\"mermaid\" aria-label=\"Raw mermaid code\"\u003esequenceDiagram\n    participant Client as Frontend\n    participant Auth as AuthController\n    participant Service as AuthService\n    participant UserRepo as UserAuthJpaAdapter\n    participant TokenRepo as RefreshTokenJpaAdapter\n    participant DB as БД\n\n    Client-\u0026gt;\u0026gt;Auth: POST /auth/login\u0026lt;br/\u0026gt;{workerCode, password}\n    Auth-\u0026gt;\u0026gt;Service: authenticate(workerCode, password)\n    Service-\u0026gt;\u0026gt;UserRepo: findByCode(workerCode)\n    UserRepo-\u0026gt;\u0026gt;DB: SELECT * FROM users WHERE code = ?\n    DB--\u0026gt;\u0026gt;UserRepo: UserEntity\n    UserRepo--\u0026gt;\u0026gt;Service: Optional\u0026lt;UserEntity\u0026gt;\n\n    alt Пользователь не найден\n        Service--\u0026gt;\u0026gt;Auth: AuthenticationException\n        Auth--\u0026gt;\u0026gt;Client: 401 Unauthorized\n    else Пользователь найден\n        Service-\u0026gt;\u0026gt;Service: BCrypt.checkpw(password, hash)\n        alt Пароль неверный\n            Service--\u0026gt;\u0026gt;Auth: AuthenticationException\n            Auth--\u0026gt;\u0026gt;Client: 401 Unauthorized\n        else Пароль верный\n            Service-\u0026gt;\u0026gt;Service: generateAccessToken(userId, role)\n            Note over Service: JWT HS256, 15 мин\u0026lt;br/\u0026gt;sub=userId, role=ROLE_*\n            Service-\u0026gt;\u0026gt;Service: generateRefreshToken()\n            Note over Service: UUID, 7 дней\n            Service-\u0026gt;\u0026gt;Service: SHA-256(refreshToken)\n            Service-\u0026gt;\u0026gt;TokenRepo: save(tokenHash, userId, expiresAt)\n            TokenRepo-\u0026gt;\u0026gt;DB: INSERT INTO refresh_tokens\n            TokenRepo--\u0026gt;\u0026gt;Service: RefreshTokenEntity\n            Service--\u0026gt;\u0026gt;Auth: AuthLoginResponseDTO\n            Auth--\u0026gt;\u0026gt;Client: 200 OK\u0026lt;br/\u0026gt;{accessToken, refreshToken, userId, code, fullName, role}\n        end\n    end\n\u003c/pre\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cspan class=\"js-render-enrichment-loader d-flex flex-justify-center flex-items-center width-full\" style=\"min-height:100px\" role=\"presentation\"\u003e\n    \u003cspan data-view-component=\"true\"\u003e\n  \u003csvg style=\"box-sizing: content-box; color: var(--color-icon-primary);\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\" data-view-component=\"true\" class=\"octospinner mx-auto tmp-mx-auto anim-rotate\"\u003e\n    \u003ccircle cx=\"8\" cy=\"8\" r=\"7\" stroke=\"currentColor\" stroke-opacity=\"0.25\" stroke-width=\"2\" vector-effect=\"non-scaling-stroke\" fill=\"none\"\u003e\u003c/circle\u003e\n    \u003cpath d=\"M15 8a7.002 7.002 0 00-7-7\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" vector-effect=\"non-scaling-stroke\"\u003e\u003c/path\u003e\n\u003c/svg\u003e    \u003cspan class=\"sr-only\"\u003eLoading\u003c/span\u003e\n\u003c/span\u003e\n  \u003c/span\u003e\n\u003c/section\u003e\n\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eПоля ответа login\u003c/h3\u003e\u003ca id=\"user-content-поля-ответа-login\" class=\"anchor\" aria-label=\"Permalink: Поля ответа login\" href=\"#поля-ответа-login\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eПоле\u003c/th\u003e\n\u003cth\u003eОписание\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003estatus\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003e\u003ccode\u003e\"success\"\u003c/code\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003euserId\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003eЧисловой ID пользователя\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003ecode\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003eWorker code (логин)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003efullName\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003eФИО сотрудника\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003erole\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003eРоль (\u003ccode\u003eADMIN\u003c/code\u003e, \u003ccode\u003eUSER\u003c/code\u003e)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003eaccessToken\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003eJWT для доступа к API\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u003ccode\u003erefreshToken\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003eUUID для обновления пары\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eЭтап 2: Использование access token\u003c/h2\u003e\u003ca id=\"user-content-этап-2-использование-access-token\" class=\"anchor\" aria-label=\"Permalink: Этап 2: Использование access token\" href=\"#этап-2-использование-access-token\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003csection class=\"js-render-needs-enrichment render-needs-enrichment position-relative\" data-identity=\"f7ea6a8b-66f7-4018-b508-389e8a1eb938\" data-host=\"https://viewscreen.githubusercontent.com\" data-src=\"https://viewscreen.githubusercontent.com/markdown/mermaid?docs_host=https%3A%2F%2Fdocs.github.com\" data-type=\"mermaid\" aria-label=\"mermaid rendered output container\"\u003e\n  \u003cdiv class=\"js-render-enrichment-target\" data-json=\"{\u0026quot;data\u0026quot;:\u0026quot;sequenceDiagram\\n    participant Client as Frontend\\n    participant API as REST API\\n    participant Filter as Security Filter\\n    participant Decoder as JwtDecoder\\n    participant Converter as JwtAuthenticationConverter\\n\\n    Client-\u0026gt;\u0026gt;API: GET /api/v1.0.0/workshops/topology\u0026lt;br/\u0026gt;Authorization: Bearer \u0026lt;accessToken\u0026gt;\\n    API-\u0026gt;\u0026gt;Filter: Проверка токена\\n    Filter-\u0026gt;\u0026gt;Decoder: decode(accessToken)\\n    Decoder-\u0026gt;\u0026gt;Decoder: Проверка подписи (HS256)\\n    Decoder-\u0026gt;\u0026gt;Decoder: Проверка issuer (scada-mobile)\\n    Decoder-\u0026gt;\u0026gt;Decoder: Проверка audience (scada-mobile-api)\\n    Decoder-\u0026gt;\u0026gt;Decoder: Проверка expiry\\n    Decoder--\u0026gt;\u0026gt;Filter: Jwt\\n    Filter-\u0026gt;\u0026gt;Converter: convert(Jwt)\\n    Converter-\u0026gt;\u0026gt;Converter: Извлечь claim \\\u0026quot;role\\\u0026quot;\\n    Converter--\u0026gt;\u0026gt;Filter: JwtAuthenticationToken (ROLE_*)\\n    Filter--\u0026gt;\u0026gt;API: Продолжить обработку\\n    API--\u0026gt;\u0026gt;Client: 200 OK + данные\\n\u0026quot;}\" data-plain=\"sequenceDiagram\n    participant Client as Frontend\n    participant API as REST API\n    participant Filter as Security Filter\n    participant Decoder as JwtDecoder\n    participant Converter as JwtAuthenticationConverter\n\n    Client-\u0026gt;\u0026gt;API: GET /api/v1.0.0/workshops/topology\u0026lt;br/\u0026gt;Authorization: Bearer \u0026lt;accessToken\u0026gt;\n    API-\u0026gt;\u0026gt;Filter: Проверка токена\n    Filter-\u0026gt;\u0026gt;Decoder: decode(accessToken)\n    Decoder-\u0026gt;\u0026gt;Decoder: Проверка подписи (HS256)\n    Decoder-\u0026gt;\u0026gt;Decoder: Проверка issuer (scada-mobile)\n    Decoder-\u0026gt;\u0026gt;Decoder: Проверка audience (scada-mobile-api)\n    Decoder-\u0026gt;\u0026gt;Decoder: Проверка expiry\n    Decoder--\u0026gt;\u0026gt;Filter: Jwt\n    Filter-\u0026gt;\u0026gt;Converter: convert(Jwt)\n    Converter-\u0026gt;\u0026gt;Converter: Извлечь claim \u0026quot;role\u0026quot;\n    Converter--\u0026gt;\u0026gt;Filter: JwtAuthenticationToken (ROLE_*)\n    Filter--\u0026gt;\u0026gt;API: Продолжить обработку\n    API--\u0026gt;\u0026gt;Client: 200 OK + данные\n\" dir=\"auto\"\u003e\n    \u003cdiv class=\"render-plaintext-hidden\" dir=\"auto\"\u003e\n      \u003cpre lang=\"mermaid\" aria-label=\"Raw mermaid code\"\u003esequenceDiagram\n    participant Client as Frontend\n    participant API as REST API\n    participant Filter as Security Filter\n    participant Decoder as JwtDecoder\n    participant Converter as JwtAuthenticationConverter\n\n    Client-\u0026gt;\u0026gt;API: GET /api/v1.0.0/workshops/topology\u0026lt;br/\u0026gt;Authorization: Bearer \u0026lt;accessToken\u0026gt;\n    API-\u0026gt;\u0026gt;Filter: Проверка токена\n    Filter-\u0026gt;\u0026gt;Decoder: decode(accessToken)\n    Decoder-\u0026gt;\u0026gt;Decoder: Проверка подписи (HS256)\n    Decoder-\u0026gt;\u0026gt;Decoder: Проверка issuer (scada-mobile)\n    Decoder-\u0026gt;\u0026gt;Decoder: Проверка audience (scada-mobile-api)\n    Decoder-\u0026gt;\u0026gt;Decoder: Проверка expiry\n    Decoder--\u0026gt;\u0026gt;Filter: Jwt\n    Filter-\u0026gt;\u0026gt;Converter: convert(Jwt)\n    Converter-\u0026gt;\u0026gt;Converter: Извлечь claim \"role\"\n    Converter--\u0026gt;\u0026gt;Filter: JwtAuthenticationToken (ROLE_*)\n    Filter--\u0026gt;\u0026gt;API: Продолжить обработку\n    API--\u0026gt;\u0026gt;Client: 200 OK + данные\n\u003c/pre\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cspan class=\"js-render-enrichment-loader d-flex flex-justify-center flex-items-center width-full\" style=\"min-height:100px\" role=\"presentation\"\u003e\n    \u003cspan data-view-component=\"true\"\u003e\n  \u003csvg style=\"box-sizing: content-box; color: var(--color-icon-primary);\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\" data-view-component=\"true\" class=\"octospinner mx-auto tmp-mx-auto anim-rotate\"\u003e\n    \u003ccircle cx=\"8\" cy=\"8\" r=\"7\" stroke=\"currentColor\" stroke-opacity=\"0.25\" stroke-width=\"2\" vector-effect=\"non-scaling-stroke\" fill=\"none\"\u003e\u003c/circle\u003e\n    \u003cpath d=\"M15 8a7.002 7.002 0 00-7-7\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" vector-effect=\"non-scaling-stroke\"\u003e\u003c/path\u003e\n\u003c/svg\u003e    \u003cspan class=\"sr-only\"\u003eLoading\u003c/span\u003e\n\u003c/span\u003e\n  \u003c/span\u003e\n\u003c/section\u003e\n\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eЭтап 3: Ротация токенов (refresh)\u003c/h2\u003e\u003ca id=\"user-content-этап-3-ротация-токенов-refresh\" class=\"anchor\" aria-label=\"Permalink: Этап 3: Ротация токенов (refresh)\" href=\"#этап-3-ротация-токенов-refresh\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003csection class=\"js-render-needs-enrichment render-needs-enrichment position-relative\" data-identity=\"39b9b295-9571-437b-8f6b-11ed4aad6edb\" data-host=\"https://viewscreen.githubusercontent.com\" data-src=\"https://viewscreen.githubusercontent.com/markdown/mermaid?docs_host=https%3A%2F%2Fdocs.github.com\" data-type=\"mermaid\" aria-label=\"mermaid rendered output container\"\u003e\n  \u003cdiv class=\"js-render-enrichment-target\" data-json=\"{\u0026quot;data\u0026quot;:\u0026quot;sequenceDiagram\\n    participant Client as Frontend\\n    participant Auth as AuthController\\n    participant Service as AuthService\\n    participant TokenRepo as RefreshTokenRepository\\n    participant DB as БД\\n\\n    Client-\u0026gt;\u0026gt;Auth: POST /auth/refresh\u0026lt;br/\u0026gt;{refreshToken}\\n    Auth-\u0026gt;\u0026gt;Service: rotate(refreshToken)\\n    Service-\u0026gt;\u0026gt;Service: SHA-256(refreshToken)\\n    Service-\u0026gt;\u0026gt;TokenRepo: findByTokenHash(hash)\\n    TokenRepo-\u0026gt;\u0026gt;DB: SELECT * FROM refresh_tokens WHERE token_hash = ?\\n    DB--\u0026gt;\u0026gt;TokenRepo: RefreshTokenEntity\\n    TokenRepo--\u0026gt;\u0026gt;Service: Optional\u0026lt;RefreshTokenEntity\u0026gt;\\n\\n    alt Токен не найден, истек или отозван\\n        Service--\u0026gt;\u0026gt;Auth: AuthenticationException\\n        Auth--\u0026gt;\u0026gt;Client: 401 Unauthorized\\n    else Токен валиден\\n        Service-\u0026gt;\u0026gt;TokenRepo: revoke(oldTokenId)\\n        TokenRepo-\u0026gt;\u0026gt;DB: UPDATE refresh_tokens SET revoked = true\\n        Service-\u0026gt;\u0026gt;Service: generateAccessToken(userId, role)\\n        Service-\u0026gt;\u0026gt;Service: generateRefreshToken()\\n        Service-\u0026gt;\u0026gt;Service: SHA-256(newRefreshToken)\\n        Service-\u0026gt;\u0026gt;TokenRepo: save(newHash, userId, expiresAt)\\n        TokenRepo-\u0026gt;\u0026gt;DB: INSERT INTO refresh_tokens\\n        Service--\u0026gt;\u0026gt;Auth: AuthRefreshResponseDTO\\n        Auth--\u0026gt;\u0026gt;Client: 200 OK\u0026lt;br/\u0026gt;{accessToken, refreshToken}\\n    end\\n\u0026quot;}\" data-plain=\"sequenceDiagram\n    participant Client as Frontend\n    participant Auth as AuthController\n    participant Service as AuthService\n    participant TokenRepo as RefreshTokenRepository\n    participant DB as БД\n\n    Client-\u0026gt;\u0026gt;Auth: POST /auth/refresh\u0026lt;br/\u0026gt;{refreshToken}\n    Auth-\u0026gt;\u0026gt;Service: rotate(refreshToken)\n    Service-\u0026gt;\u0026gt;Service: SHA-256(refreshToken)\n    Service-\u0026gt;\u0026gt;TokenRepo: findByTokenHash(hash)\n    TokenRepo-\u0026gt;\u0026gt;DB: SELECT * FROM refresh_tokens WHERE token_hash = ?\n    DB--\u0026gt;\u0026gt;TokenRepo: RefreshTokenEntity\n    TokenRepo--\u0026gt;\u0026gt;Service: Optional\u0026lt;RefreshTokenEntity\u0026gt;\n\n    alt Токен не найден, истек или отозван\n        Service--\u0026gt;\u0026gt;Auth: AuthenticationException\n        Auth--\u0026gt;\u0026gt;Client: 401 Unauthorized\n    else Токен валиден\n        Service-\u0026gt;\u0026gt;TokenRepo: revoke(oldTokenId)\n        TokenRepo-\u0026gt;\u0026gt;DB: UPDATE refresh_tokens SET revoked = true\n        Service-\u0026gt;\u0026gt;Service: generateAccessToken(userId, role)\n        Service-\u0026gt;\u0026gt;Service: generateRefreshToken()\n        Service-\u0026gt;\u0026gt;Service: SHA-256(newRefreshToken)\n        Service-\u0026gt;\u0026gt;TokenRepo: save(newHash, userId, expiresAt)\n        TokenRepo-\u0026gt;\u0026gt;DB: INSERT INTO refresh_tokens\n        Service--\u0026gt;\u0026gt;Auth: AuthRefreshResponseDTO\n        Auth--\u0026gt;\u0026gt;Client: 200 OK\u0026lt;br/\u0026gt;{accessToken, refreshToken}\n    end\n\" dir=\"auto\"\u003e\n    \u003cdiv class=\"render-plaintext-hidden\" dir=\"auto\"\u003e\n      \u003cpre lang=\"mermaid\" aria-label=\"Raw mermaid code\"\u003esequenceDiagram\n    participant Client as Frontend\n    participant Auth as AuthController\n    participant Service as AuthService\n    participant TokenRepo as RefreshTokenRepository\n    participant DB as БД\n\n    Client-\u0026gt;\u0026gt;Auth: POST /auth/refresh\u0026lt;br/\u0026gt;{refreshToken}\n    Auth-\u0026gt;\u0026gt;Service: rotate(refreshToken)\n    Service-\u0026gt;\u0026gt;Service: SHA-256(refreshToken)\n    Service-\u0026gt;\u0026gt;TokenRepo: findByTokenHash(hash)\n    TokenRepo-\u0026gt;\u0026gt;DB: SELECT * FROM refresh_tokens WHERE token_hash = ?\n    DB--\u0026gt;\u0026gt;TokenRepo: RefreshTokenEntity\n    TokenRepo--\u0026gt;\u0026gt;Service: Optional\u0026lt;RefreshTokenEntity\u0026gt;\n\n    alt Токен не найден, истек или отозван\n        Service--\u0026gt;\u0026gt;Auth: AuthenticationException\n        Auth--\u0026gt;\u0026gt;Client: 401 Unauthorized\n    else Токен валиден\n        Service-\u0026gt;\u0026gt;TokenRepo: revoke(oldTokenId)\n        TokenRepo-\u0026gt;\u0026gt;DB: UPDATE refresh_tokens SET revoked = true\n        Service-\u0026gt;\u0026gt;Service: generateAccessToken(userId, role)\n        Service-\u0026gt;\u0026gt;Service: generateRefreshToken()\n        Service-\u0026gt;\u0026gt;Service: SHA-256(newRefreshToken)\n        Service-\u0026gt;\u0026gt;TokenRepo: save(newHash, userId, expiresAt)\n        TokenRepo-\u0026gt;\u0026gt;DB: INSERT INTO refresh_tokens\n        Service--\u0026gt;\u0026gt;Auth: AuthRefreshResponseDTO\n        Auth--\u0026gt;\u0026gt;Client: 200 OK\u0026lt;br/\u0026gt;{accessToken, refreshToken}\n    end\n\u003c/pre\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cspan class=\"js-render-enrichment-loader d-flex flex-justify-center flex-items-center width-full\" style=\"min-height:100px\" role=\"presentation\"\u003e\n    \u003cspan data-view-component=\"true\"\u003e\n  \u003csvg style=\"box-sizing: content-box; color: var(--color-icon-primary);\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\" data-view-component=\"true\" class=\"octospinner mx-auto tmp-mx-auto anim-rotate\"\u003e\n    \u003ccircle cx=\"8\" cy=\"8\" r=\"7\" stroke=\"currentColor\" stroke-opacity=\"0.25\" stroke-width=\"2\" vector-effect=\"non-scaling-stroke\" fill=\"none\"\u003e\u003c/circle\u003e\n    \u003cpath d=\"M15 8a7.002 7.002 0 00-7-7\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" vector-effect=\"non-scaling-stroke\"\u003e\u003c/path\u003e\n\u003c/svg\u003e    \u003cspan class=\"sr-only\"\u003eLoading\u003c/span\u003e\n\u003c/span\u003e\n  \u003c/span\u003e\n\u003c/section\u003e\n\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eПочему ротация важна\u003c/h3\u003e\u003ca id=\"user-content-почему-ротация-важна\" class=\"anchor\" aria-label=\"Permalink: Почему ротация важна\" href=\"#почему-ротация-важна\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003eПри компрометации access token злоумышленник имеет доступ максимум 15 минут.\u003c/li\u003e\n\u003cli\u003eПри компрометации refresh token он будет отозван при следующем легитимном использовании.\u003c/li\u003e\n\u003cli\u003eКаждое обновление генерирует новую пару — старая пара становится недействительной.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eЭтап 4: Выход (logout)\u003c/h2\u003e\u003ca id=\"user-content-этап-4-выход-logout\" class=\"anchor\" aria-label=\"Permalink: Этап 4: Выход (logout)\" href=\"#этап-4-выход-logout\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003csection class=\"js-render-needs-enrichment render-needs-enrichment position-relative\" data-identity=\"775d1074-043b-4bb5-822b-9aff72a5d3b4\" data-host=\"https://viewscreen.githubusercontent.com\" data-src=\"https://viewscreen.githubusercontent.com/markdown/mermaid?docs_host=https%3A%2F%2Fdocs.github.com\" data-type=\"mermaid\" aria-label=\"mermaid rendered output container\"\u003e\n  \u003cdiv class=\"js-render-enrichment-target\" data-json=\"{\u0026quot;data\u0026quot;:\u0026quot;sequenceDiagram\\n    participant Client as Frontend\\n    participant Auth as AuthController\\n    participant Service as AuthService\\n    participant TokenRepo as RefreshTokenRepository\\n    participant DB as БД\\n\\n    Client-\u0026gt;\u0026gt;Auth: POST /auth/logout\u0026lt;br/\u0026gt;{refreshToken}\\n    Auth-\u0026gt;\u0026gt;Service: revoke(refreshToken)\\n    Service-\u0026gt;\u0026gt;Service: SHA-256(refreshToken)\\n    Service-\u0026gt;\u0026gt;TokenRepo: revokeByHash(hash)\\n    TokenRepo-\u0026gt;\u0026gt;DB: UPDATE refresh_tokens SET revoked = true\\n    TokenRepo--\u0026gt;\u0026gt;Service: OK\\n    Service--\u0026gt;\u0026gt;Auth: OK\\n    Auth--\u0026gt;\u0026gt;Client: 200 OK\\n    Client-\u0026gt;\u0026gt;Client: Удалить токены из localStorage\\n\u0026quot;}\" data-plain=\"sequenceDiagram\n    participant Client as Frontend\n    participant Auth as AuthController\n    participant Service as AuthService\n    participant TokenRepo as RefreshTokenRepository\n    participant DB as БД\n\n    Client-\u0026gt;\u0026gt;Auth: POST /auth/logout\u0026lt;br/\u0026gt;{refreshToken}\n    Auth-\u0026gt;\u0026gt;Service: revoke(refreshToken)\n    Service-\u0026gt;\u0026gt;Service: SHA-256(refreshToken)\n    Service-\u0026gt;\u0026gt;TokenRepo: revokeByHash(hash)\n    TokenRepo-\u0026gt;\u0026gt;DB: UPDATE refresh_tokens SET revoked = true\n    TokenRepo--\u0026gt;\u0026gt;Service: OK\n    Service--\u0026gt;\u0026gt;Auth: OK\n    Auth--\u0026gt;\u0026gt;Client: 200 OK\n    Client-\u0026gt;\u0026gt;Client: Удалить токены из localStorage\n\" dir=\"auto\"\u003e\n    \u003cdiv class=\"render-plaintext-hidden\" dir=\"auto\"\u003e\n      \u003cpre lang=\"mermaid\" aria-label=\"Raw mermaid code\"\u003esequenceDiagram\n    participant Client as Frontend\n    participant Auth as AuthController\n    participant Service as AuthService\n    participant TokenRepo as RefreshTokenRepository\n    participant DB as БД\n\n    Client-\u0026gt;\u0026gt;Auth: POST /auth/logout\u0026lt;br/\u0026gt;{refreshToken}\n    Auth-\u0026gt;\u0026gt;Service: revoke(refreshToken)\n    Service-\u0026gt;\u0026gt;Service: SHA-256(refreshToken)\n    Service-\u0026gt;\u0026gt;TokenRepo: revokeByHash(hash)\n    TokenRepo-\u0026gt;\u0026gt;DB: UPDATE refresh_tokens SET revoked = true\n    TokenRepo--\u0026gt;\u0026gt;Service: OK\n    Service--\u0026gt;\u0026gt;Auth: OK\n    Auth--\u0026gt;\u0026gt;Client: 200 OK\n    Client-\u0026gt;\u0026gt;Client: Удалить токены из localStorage\n\u003c/pre\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cspan class=\"js-render-enrichment-loader d-flex flex-justify-center flex-items-center width-full\" style=\"min-height:100px\" role=\"presentation\"\u003e\n    \u003cspan data-view-component=\"true\"\u003e\n  \u003csvg style=\"box-sizing: content-box; color: var(--color-icon-primary);\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\" data-view-component=\"true\" class=\"octospinner mx-auto tmp-mx-auto anim-rotate\"\u003e\n    \u003ccircle cx=\"8\" cy=\"8\" r=\"7\" stroke=\"currentColor\" stroke-opacity=\"0.25\" stroke-width=\"2\" vector-effect=\"non-scaling-stroke\" fill=\"none\"\u003e\u003c/circle\u003e\n    \u003cpath d=\"M15 8a7.002 7.002 0 00-7-7\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" vector-effect=\"non-scaling-stroke\"\u003e\u003c/path\u003e\n\u003c/svg\u003e    \u003cspan class=\"sr-only\"\u003eLoading\u003c/span\u003e\n\u003c/span\u003e\n  \u003c/span\u003e\n\u003c/section\u003e\n\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eЭтап 5: WebSocket аутентификация\u003c/h2\u003e\u003ca id=\"user-content-этап-5-websocket-аутентификация\" class=\"anchor\" aria-label=\"Permalink: Этап 5: WebSocket аутентификация\" href=\"#этап-5-websocket-аутентификация\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003csection class=\"js-render-needs-enrichment render-needs-enrichment position-relative\" data-identity=\"3f344e3d-7be8-4b84-902b-d592342c8abc\" data-host=\"https://viewscreen.githubusercontent.com\" data-src=\"https://viewscreen.githubusercontent.com/markdown/mermaid?docs_host=https%3A%2F%2Fdocs.github.com\" data-type=\"mermaid\" aria-label=\"mermaid rendered output container\"\u003e\n  \u003cdiv class=\"js-render-enrichment-target\" data-json=\"{\u0026quot;data\u0026quot;:\u0026quot;sequenceDiagram\\n    participant Client as Frontend\\n    participant Ws as WebSocket Endpoint\\n    participant Interceptor as WebSocketJwtInterceptor\\n    participant JwtProvider as JwtTokenProvider\\n\\n    Client-\u0026gt;\u0026gt;Ws: GET /ws/live?token=\u0026lt;accessToken\u0026gt;\\n    Ws-\u0026gt;\u0026gt;Interceptor: beforeHandshake(request, response, handler, attributes)\\n    Interceptor-\u0026gt;\u0026gt;Interceptor: Извлечь token из query param\\n    Interceptor-\u0026gt;\u0026gt;JwtProvider: validateToken(token)\\n    JwtProvider--\u0026gt;\u0026gt;Interceptor: userId (или исключение)\\n\\n    alt Токен невалиден\\n        Interceptor--\u0026gt;\u0026gt;Ws: false (отклонить handshake)\\n        Ws--\u0026gt;\u0026gt;Client: 401 Unauthorized\\n    else Токен валиден\\n        Interceptor-\u0026gt;\u0026gt;Interceptor: attributes.put(\\\u0026quot;userId\\\u0026quot;, userId)\\n        Interceptor--\u0026gt;\u0026gt;Ws: true (продолжить handshake)\\n        Ws--\u0026gt;\u0026gt;Client: 101 Switching Protocols\\n    end\\n\u0026quot;}\" data-plain=\"sequenceDiagram\n    participant Client as Frontend\n    participant Ws as WebSocket Endpoint\n    participant Interceptor as WebSocketJwtInterceptor\n    participant JwtProvider as JwtTokenProvider\n\n    Client-\u0026gt;\u0026gt;Ws: GET /ws/live?token=\u0026lt;accessToken\u0026gt;\n    Ws-\u0026gt;\u0026gt;Interceptor: beforeHandshake(request, response, handler, attributes)\n    Interceptor-\u0026gt;\u0026gt;Interceptor: Извлечь token из query param\n    Interceptor-\u0026gt;\u0026gt;JwtProvider: validateToken(token)\n    JwtProvider--\u0026gt;\u0026gt;Interceptor: userId (или исключение)\n\n    alt Токен невалиден\n        Interceptor--\u0026gt;\u0026gt;Ws: false (отклонить handshake)\n        Ws--\u0026gt;\u0026gt;Client: 401 Unauthorized\n    else Токен валиден\n        Interceptor-\u0026gt;\u0026gt;Interceptor: attributes.put(\u0026quot;userId\u0026quot;, userId)\n        Interceptor--\u0026gt;\u0026gt;Ws: true (продолжить handshake)\n        Ws--\u0026gt;\u0026gt;Client: 101 Switching Protocols\n    end\n\" dir=\"auto\"\u003e\n    \u003cdiv class=\"render-plaintext-hidden\" dir=\"auto\"\u003e\n      \u003cpre lang=\"mermaid\" aria-label=\"Raw mermaid code\"\u003esequenceDiagram\n    participant Client as Frontend\n    participant Ws as WebSocket Endpoint\n    participant Interceptor as WebSocketJwtInterceptor\n    participant JwtProvider as JwtTokenProvider\n\n    Client-\u0026gt;\u0026gt;Ws: GET /ws/live?token=\u0026lt;accessToken\u0026gt;\n    Ws-\u0026gt;\u0026gt;Interceptor: beforeHandshake(request, response, handler, attributes)\n    Interceptor-\u0026gt;\u0026gt;Interceptor: Извлечь token из query param\n    Interceptor-\u0026gt;\u0026gt;JwtProvider: validateToken(token)\n    JwtProvider--\u0026gt;\u0026gt;Interceptor: userId (или исключение)\n\n    alt Токен невалиден\n        Interceptor--\u0026gt;\u0026gt;Ws: false (отклонить handshake)\n        Ws--\u0026gt;\u0026gt;Client: 401 Unauthorized\n    else Токен валиден\n        Interceptor-\u0026gt;\u0026gt;Interceptor: attributes.put(\"userId\", userId)\n        Interceptor--\u0026gt;\u0026gt;Ws: true (продолжить handshake)\n        Ws--\u0026gt;\u0026gt;Client: 101 Switching Protocols\n    end\n\u003c/pre\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cspan class=\"js-render-enrichment-loader d-flex flex-justify-center flex-items-center width-full\" style=\"min-height:100px\" role=\"presentation\"\u003e\n    \u003cspan data-view-component=\"true\"\u003e\n  \u003csvg style=\"box-sizing: content-box; color: var(--color-icon-primary);\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\" data-view-component=\"true\" class=\"octospinner mx-auto tmp-mx-auto anim-rotate\"\u003e\n    \u003ccircle cx=\"8\" cy=\"8\" r=\"7\" stroke=\"currentColor\" stroke-opacity=\"0.25\" stroke-width=\"2\" vector-effect=\"non-scaling-stroke\" fill=\"none\"\u003e\u003c/circle\u003e\n    \u003cpath d=\"M15 8a7.002 7.002 0 00-7-7\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" vector-effect=\"non-scaling-stroke\"\u003e\u003c/path\u003e\n\u003c/svg\u003e    \u003cspan class=\"sr-only\"\u003eLoading\u003c/span\u003e\n\u003c/span\u003e\n  \u003c/span\u003e\n\u003c/section\u003e\n\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eЭтап 6: Доступ к админ-панели\u003c/h2\u003e\u003ca id=\"user-content-этап-6-доступ-к-админ-панели\" class=\"anchor\" aria-label=\"Permalink: Этап 6: Доступ к админ-панели\" href=\"#этап-6-доступ-к-админ-панели\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003csection class=\"js-render-needs-enrichment render-needs-enrichment position-relative\" data-identity=\"dce95233-3df3-446c-936a-0cb1f8cbd872\" data-host=\"https://viewscreen.githubusercontent.com\" data-src=\"https://viewscreen.githubusercontent.com/markdown/mermaid?docs_host=https%3A%2F%2Fdocs.github.com\" data-type=\"mermaid\" aria-label=\"mermaid rendered output container\"\u003e\n  \u003cdiv class=\"js-render-enrichment-target\" data-json=\"{\u0026quot;data\u0026quot;:\u0026quot;sequenceDiagram\\n    participant Client as Frontend\\n    participant Admin as AdminUserController\\n    participant Security as Spring Security\\n\\n    Client-\u0026gt;\u0026gt;Admin: GET /api/v1.0.0/admin/users/list\u0026lt;br/\u0026gt;Authorization: Bearer \u0026lt;token\u0026gt;\\n    Admin-\u0026gt;\u0026gt;Security: @PreAuthorize(\\\u0026quot;hasRole('ADMIN')\\\u0026quot;)\\n    Security-\u0026gt;\u0026gt;Security: Извлечь роль из JWT\\n\\n    alt Роль != ADMIN\\n        Security--\u0026gt;\u0026gt;Admin: AccessDeniedException\\n        Admin--\u0026gt;\u0026gt;Client: 403 Forbidden\\n    else Роль == ADMIN\\n        Security--\u0026gt;\u0026gt;Admin: Продолжить\\n        Admin--\u0026gt;\u0026gt;Client: 200 OK + список пользователей\\n    end\\n\u0026quot;}\" data-plain=\"sequenceDiagram\n    participant Client as Frontend\n    participant Admin as AdminUserController\n    participant Security as Spring Security\n\n    Client-\u0026gt;\u0026gt;Admin: GET /api/v1.0.0/admin/users/list\u0026lt;br/\u0026gt;Authorization: Bearer \u0026lt;token\u0026gt;\n    Admin-\u0026gt;\u0026gt;Security: @PreAuthorize(\u0026quot;hasRole('ADMIN')\u0026quot;)\n    Security-\u0026gt;\u0026gt;Security: Извлечь роль из JWT\n\n    alt Роль != ADMIN\n        Security--\u0026gt;\u0026gt;Admin: AccessDeniedException\n        Admin--\u0026gt;\u0026gt;Client: 403 Forbidden\n    else Роль == ADMIN\n        Security--\u0026gt;\u0026gt;Admin: Продолжить\n        Admin--\u0026gt;\u0026gt;Client: 200 OK + список пользователей\n    end\n\" dir=\"auto\"\u003e\n    \u003cdiv class=\"render-plaintext-hidden\" dir=\"auto\"\u003e\n      \u003cpre lang=\"mermaid\" aria-label=\"Raw mermaid code\"\u003esequenceDiagram\n    participant Client as Frontend\n    participant Admin as AdminUserController\n    participant Security as Spring Security\n\n    Client-\u0026gt;\u0026gt;Admin: GET /api/v1.0.0/admin/users/list\u0026lt;br/\u0026gt;Authorization: Bearer \u0026lt;token\u0026gt;\n    Admin-\u0026gt;\u0026gt;Security: @PreAuthorize(\"hasRole('ADMIN')\")\n    Security-\u0026gt;\u0026gt;Security: Извлечь роль из JWT\n\n    alt Роль != ADMIN\n        Security--\u0026gt;\u0026gt;Admin: AccessDeniedException\n        Admin--\u0026gt;\u0026gt;Client: 403 Forbidden\n    else Роль == ADMIN\n        Security--\u0026gt;\u0026gt;Admin: Продолжить\n        Admin--\u0026gt;\u0026gt;Client: 200 OK + список пользователей\n    end\n\u003c/pre\u003e\n    \u003c/div\u003e\n  \u003c/div\u003e\n  \u003cspan class=\"js-render-enrichment-loader d-flex flex-justify-center flex-items-center width-full\" style=\"min-height:100px\" role=\"presentation\"\u003e\n    \u003cspan data-view-component=\"true\"\u003e\n  \u003csvg style=\"box-sizing: content-box; color: var(--color-icon-primary);\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" aria-hidden=\"true\" data-view-component=\"true\" class=\"octospinner mx-auto tmp-mx-auto anim-rotate\"\u003e\n    \u003ccircle cx=\"8\" cy=\"8\" r=\"7\" stroke=\"currentColor\" stroke-opacity=\"0.25\" stroke-width=\"2\" vector-effect=\"non-scaling-stroke\" fill=\"none\"\u003e\u003c/circle\u003e\n    \u003cpath d=\"M15 8a7.002 7.002 0 00-7-7\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" vector-effect=\"non-scaling-stroke\"\u003e\u003c/path\u003e\n\u003c/svg\u003e    \u003cspan class=\"sr-only\"\u003eLoading\u003c/span\u003e\n\u003c/span\u003e\n  \u003c/span\u003e\n\u003c/section\u003e\n\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch2 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eСравнение токенов\u003c/h2\u003e\u003ca id=\"user-content-сравнение-токенов\" class=\"anchor\" aria-label=\"Permalink: Сравнение токенов\" href=\"#сравнение-токенов\"\u003e\u003csvg data-component=\"Octicon\" class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003eХарактеристика\u003c/th\u003e\n\u003cth\u003eAccess Token\u003c/th\u003e\n\u003cth\u003eRefresh Token\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003eФормат\u003c/td\u003e\n\u003ctd\u003eJWT (HS256)\u003c/td\u003e\n\u003ctd\u003eUUID (plain text)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eСрок действия\u003c/td\u003e\n\u003ctd\u003e15 минут\u003c/td\u003e\n\u003ctd\u003e7 дней\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eХранение на клиенте\u003c/td\u003e\n\u003ctd\u003elocalStorage\u003c/td\u003e\n\u003ctd\u003elocalStorage\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eХранение на сервере\u003c/td\u003e\n\u003ctd\u003eНе хранится\u003c/td\u003e\n\u003ctd\u003eSHA-256 хеш в БД\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eНазначение\u003c/td\u003e\n\u003ctd\u003eДоступ к API\u003c/td\u003e\n\u003ctd\u003eПолучение новой пары\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eПередача\u003c/td\u003e\n\u003ctd\u003eHeader \u003ccode\u003eAuthorization: Bearer\u003c/code\u003e\u003c/td\u003e\n\u003ctd\u003eBody JSON\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eОтзыв\u003c/td\u003e\n\u003ctd\u003eНевозможен (stateless)\u003c/td\u003e\n\u003ctd\u003eВозможен (флаг \u003ccode\u003erevoked\u003c/code\u003e)\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eРотация\u003c/td\u003e\n\u003ctd\u003eПри каждом refresh\u003c/td\u003e\n\u003ctd\u003eПри каждом refresh\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\u003c/article\u003e","richTextTruncated":false,"renderedFileInfo":null,"symbols":{"timed_out":false,"not_analyzed":false,"symbols":[{"name":"Поток аутентификации (SCADA Mobile)","fully_qualified_name":"Поток аутентификации (SCADA Mobile)","kind":"section_1","ident_start":2,"ident_end":56,"extent_start":0,"extent_end":10837,"ident_utf16":{"start":{"line_number":0,"utf16_col":2},"end":{"line_number":0,"utf16_col":37}},"extent_utf16":{"start":{"line_number":0,"utf16_col":0},"end":{"line_number":268,"utf16_col":0}}},{"name":"Purpose","fully_qualified_name":"Purpose","kind":"section_2","ident_start":61,"ident_end":68,"extent_start":58,"extent_end":264,"ident_utf16":{"start":{"line_number":2,"utf16_col":3},"end":{"line_number":2,"utf16_col":10}},"extent_utf16":{"start":{"line_number":2,"utf16_col":0},"end":{"line_number":5,"utf16_col":0}}},{"name":"Table of contents","fully_qualified_name":"Table of contents","kind":"section_2","ident_start":267,"ident_end":284,"extent_start":264,"extent_end":1008,"ident_utf16":{"start":{"line_number":5,"utf16_col":3},"end":{"line_number":5,"utf16_col":20}},"extent_utf16":{"start":{"line_number":5,"utf16_col":0},"end":{"line_number":16,"utf16_col":0}}},{"name":"Общая диаграмма","fully_qualified_name":"Общая диаграмма","kind":"section_2","ident_start":1011,"ident_end":1040,"extent_start":1008,"extent_end":2896,"ident_utf16":{"start":{"line_number":16,"utf16_col":3},"end":{"line_number":16,"utf16_col":18}},"extent_utf16":{"start":{"line_number":16,"utf16_col":0},"end":{"line_number":71,"utf16_col":0}}},{"name":"Этап 1: Вход (login)","fully_qualified_name":"Этап 1: Вход (login)","kind":"section_2","ident_start":2899,"ident_end":2927,"extent_start":2896,"extent_end":4978,"ident_utf16":{"start":{"line_number":71,"utf16_col":3},"end":{"line_number":71,"utf16_col":23}},"extent_utf16":{"start":{"line_number":71,"utf16_col":0},"end":{"line_number":124,"utf16_col":0}}},{"name":"Поля ответа login","fully_qualified_name":"Поля ответа login","kind":"section_3","ident_start":4570,"ident_end":4597,"extent_start":4566,"extent_end":4978,"ident_utf16":{"start":{"line_number":112,"utf16_col":4},"end":{"line_number":112,"utf16_col":21}},"extent_utf16":{"start":{"line_number":112,"utf16_col":0},"end":{"line_number":124,"utf16_col":0}}},{"name":"Этап 2: Использование access token","fully_qualified_name":"Этап 2: Использование access token","kind":"section_2","ident_start":4981,"ident_end":5032,"extent_start":4978,"extent_end":5969,"ident_utf16":{"start":{"line_number":124,"utf16_col":3},"end":{"line_number":124,"utf16_col":37}},"extent_utf16":{"start":{"line_number":124,"utf16_col":0},"end":{"line_number":149,"utf16_col":0}}},{"name":"Этап 3: Ротация токенов (refresh)","fully_qualified_name":"Этап 3: Ротация токенов (refresh)","kind":"section_2","ident_start":5972,"ident_end":6023,"extent_start":5969,"extent_end":7801,"ident_utf16":{"start":{"line_number":149,"utf16_col":3},"end":{"line_number":149,"utf16_col":36}},"extent_utf16":{"start":{"line_number":149,"utf16_col":0},"end":{"line_number":189,"utf16_col":0}}},{"name":"Почему ротация важна","fully_qualified_name":"Почему ротация важна","kind":"section_3","ident_start":7314,"ident_end":7352,"extent_start":7310,"extent_end":7801,"ident_utf16":{"start":{"line_number":183,"utf16_col":4},"end":{"line_number":183,"utf16_col":24}},"extent_utf16":{"start":{"line_number":183,"utf16_col":0},"end":{"line_number":189,"utf16_col":0}}},{"name":"Этап 4: Выход (logout)","fully_qualified_name":"Этап 4: Выход (logout)","kind":"section_2","ident_start":7804,"ident_end":7835,"extent_start":7801,"extent_end":8456,"ident_utf16":{"start":{"line_number":189,"utf16_col":3},"end":{"line_number":189,"utf16_col":25}},"extent_utf16":{"start":{"line_number":189,"utf16_col":0},"end":{"line_number":210,"utf16_col":0}}},{"name":"Этап 5: WebSocket аутентификация","fully_qualified_name":"Этап 5: WebSocket аутентификация","kind":"section_2","ident_start":8459,"ident_end":8509,"extent_start":8456,"extent_end":9405,"ident_utf16":{"start":{"line_number":210,"utf16_col":3},"end":{"line_number":210,"utf16_col":35}},"extent_utf16":{"start":{"line_number":210,"utf16_col":0},"end":{"line_number":235,"utf16_col":0}}},{"name":"Этап 6: Доступ к админ-панели","fully_qualified_name":"Этап 6: Доступ к админ-панели","kind":"section_2","ident_start":9408,"ident_end":9459,"extent_start":9405,"extent_end":10091,"ident_utf16":{"start":{"line_number":235,"utf16_col":3},"end":{"line_number":235,"utf16_col":32}},"extent_utf16":{"start":{"line_number":235,"utf16_col":0},"end":{"line_number":256,"utf16_col":0}}},{"name":"Сравнение токенов","fully_qualified_name":"Сравнение токенов","kind":"section_2","ident_start":10094,"ident_end":10127,"extent_start":10091,"extent_end":10837,"ident_utf16":{"start":{"line_number":256,"utf16_col":3},"end":{"line_number":256,"utf16_col":20}},"extent_utf16":{"start":{"line_number":256,"utf16_col":0},"end":{"line_number":268,"utf16_col":0}}}]}},"codeViewLayoutRoute":{"repo":{"id":1189419502,"defaultBranch":"main","name":"scada-mobile","ownerLogin":"gleb7499","currentUserCanPush":false,"isFork":true,"isEmpty":false,"createdAt":"2026-03-23T09:56:10.000Z","ownerAvatar":"https://avatars.githubusercontent.com/u/164905074?v=4","public":true,"private":false,"isOrgOwned":false,"isArchived":false},"currentUser":null,"uploadToken":"DFlOhIyzH4u020yLTC5djT7wAfWfd5GKCeMc5wEpV60r5TV59EzPAg1w4hDj-QhIx0WE0u0qDzOuFS3sAb0LBg","allShortcutsEnabled":false,"treeExpanded":true,"path":"AUTH_FLOW.md","symbolsExpanded":false,"refInfo":{"name":"main","listCacheKey":"v0:1774259771.488252","canEdit":false,"currentOid":"b3f4fc727c6e5f7bd045955bb99642e4df6f62d8"},"helpUrl":"https://docs.github.com","githubDevUrl":null},"codeViewFileTreeLayoutRoute":{"fileTree":{"":{"items":[{"name":".github","path":".github","contentType":"directory"},{"name":".kimi","path":".kimi","contentType":"directory"},{"name":"backend","path":"backend","contentType":"directory"},{"name":"frontend","path":"frontend","contentType":"directory"},{"name":"screenshots-from-pm","path":"screenshots-from-pm","contentType":"directory"},{"name":"screenshots","path":"screenshots","contentType":"directory"},{"name":"scripts","path":"scripts","contentType":"directory"},{"name":".dockerignore","path":".dockerignore","contentType":"file"},{"name":".editorconfig","path":".editorconfig","contentType":"file"},{"name":".env.prod.example","path":".env.prod.example","contentType":"file"},{"name":".gitattributes","path":".gitattributes","contentType":"file"},{"name":".gitignore","path":".gitignore","contentType":"file"},{"name":"AGENTS.md","path":"AGENTS.md","contentType":"file"},{"name":"ALERT_LIFECYCLE.md","path":"ALERT_LIFECYCLE.md","contentType":"file"},{"name":"API_REFERENCE.md","path":"API_REFERENCE.md","contentType":"file"},{"name":"AUTH_FLOW.md","path":"AUTH_FLOW.md","contentType":"file"},{"name":"BACKEND_ARCHITECTURE.md","path":"BACKEND_ARCHITECTURE.md","contentType":"file"},{"name":"BACKEND_COMPONENT_DIAGRAM.md","path":"BACKEND_COMPONENT_DIAGRAM.md","contentType":"file"},{"name":"BACKEND_DATA_FLOW.md","path":"BACKEND_DATA_FLOW.md","contentType":"file"},{"name":"FRONTEND_API.md","path":"FRONTEND_API.md","contentType":"file"},{"name":"FRONTEND_ARCHITECTURE.md","path":"FRONTEND_ARCHITECTURE.md","contentType":"file"},{"name":"FRONTEND_DATA_SOURCES.md","path":"FRONTEND_DATA_SOURCES.md","contentType":"file"},{"name":"MAKEFILE.md","path":"MAKEFILE.md","contentType":"file"},{"name":"Makefile","path":"Makefile","contentType":"file"},{"name":"NOTIFICATIONS_ARCHITECTURE.md","path":"NOTIFICATIONS_ARCHITECTURE.md","contentType":"file"},{"name":"PRINTSR_USED_TAGS.md","path":"PRINTSR_USED_TAGS.md","contentType":"file"},{"name":"PROJECT_DIAGRAM.md","path":"PROJECT_DIAGRAM.md","contentType":"file"},{"name":"README.en.md","path":"README.en.md","contentType":"file"},{"name":"README.md","path":"README.md","contentType":"file"},{"name":"RUN_PROJECT_DOCKER.md","path":"RUN_PROJECT_DOCKER.md","contentType":"file"},{"name":"SCADA_MACHINE_API.md","path":"SCADA_MACHINE_API.md","contentType":"file"},{"name":"SECURITY.md","path":"SECURITY.md","contentType":"file"},{"name":"STRUCTURE.md","path":"STRUCTURE.md","contentType":"file"},{"name":"TEST_ACCOUNTS.md","path":"TEST_ACCOUNTS.md","contentType":"file"},{"name":"api_mapping.md","path":"api_mapping.md","contentType":"file"},{"name":"docker-compose.yml","path":"docker-compose.yml","contentType":"file"},{"name":"ТЗ_Gateway_Якимовец.md","path":"ТЗ_Gateway_Якимовец.md","contentType":"file"},{"name":"ТЗ_исправление_runtime_счетчиков_и_ошибок.md","path":"ТЗ_исправление_runtime_счетчиков_и_ошибок.md","contentType":"file"},{"name":"ТЗ_миграция_на_Kafka.md","path":"ТЗ_миграция_на_Kafka.md","contentType":"file"}],"totalCount":39}},"fileTreeProcessingTime":65.098713,"foldersToFetch":[]},"codeViewBlobLayoutRoute":{"codeLineWrapEnabled":false,"refInfo":{"name":"main","listCacheKey":"v0:1774259771.488252","canEdit":false,"refType":"branch","currentOid":"b3f4fc727c6e5f7bd045955bb99642e4df6f62d8","canEditOnDefaultBranch":false,"fileExistsOnDefault":true},"path":"AUTH_FLOW.md","blob":{"copilotSWEAgentEnabled":false,"dependabotInfo":{"showConfigurationBanner":false,"configFilePath":null,"networkDependabotPath":"/gleb7499/scada-mobile/network/updates","dismissConfigurationNoticePath":"/settings/dismiss-notice/dependabot_configuration_notice","configurationNoticeDismissed":null},"displayName":"AUTH_FLOW.md","displayUrl":"https://github.com/gleb7499/scada-mobile/blob/main/AUTH_FLOW.md?raw=true","headerInfo":{"blobSize":"10.6 KB","deleteTooltip":"You must be signed in to make or propose changes","editTooltip":"You must be signed in to make or propose changes","ghDesktopPath":"https://desktop.github.com","isGitLfs":false,"onBranch":true,"shortPath":"79b2858","siteNavLoginPath":"/login?return_to=https%3A%2F%2Fgithub.com%2Fgleb7499%2Fscada-mobile%2Fblob%2Fmain%2FAUTH_FLOW.md","isCSV":false,"isRichtext":true,"lineInfo":{"truncatedLoc":"268","truncatedSloc":"230"},"mode":"file"},"image":false,"isCodeownersFile":null,"isPlain":false,"isValidLegacyIssueTemplate":false,"isIssueTemplate":false,"isDiscussionTemplate":false,"language":"Markdown","languageID":222,"large":false,"planSupportInfo":{"repoIsFork":null,"repoOwnedByCurrentUser":null,"requestFullPath":"/gleb7499/scada-mobile/blob/main/AUTH_FLOW.md","showFreeOrgGatedFeatureMessage":null,"showPlanSupportBanner":null,"upgradeDataAttributes":null,"upgradePath":null},"publishBannersInfo":{"dismissActionNoticePath":"/settings/dismiss-notice/publish_action_from_dockerfile","releasePath":"/gleb7499/scada-mobile/releases/new?marketplace=true","showPublishActionBanner":false},"rawBlobUrl":"https://github.com/gleb7499/scada-mobile/raw/refs/heads/main/AUTH_FLOW.md","renderImageOrRaw":false,"shortPath":null,"symbolsEnabled":true,"tabSize":4,"topBannersInfo":{"overridingGlobalFundingFile":false,"globalPreferredFundingPath":null,"showInvalidCitationWarning":false,"citationHelpUrl":"https://docs.github.com/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-citation-files","actionsOnboardingTip":null},"truncated":false,"viewable":true,"workflowRedirectUrl":null},"copilotInfo":null,"copilotAccessAllowed":false,"copilotSpacesEnabled":false,"modelsAccessAllowed":false,"modelsRepoIntegrationEnabled":false,"isMarketplaceEnabled":true},"codeViewBlobLayoutRoute.StyledBlob":{"rawLines":["# Поток аутентификации (SCADA Mobile)","","## Purpose","Документ описывает полный поток аутентификации и авторизации: от входа пользователя до WebSocket-соединения.","","## Table of contents","- [Purpose](#purpose)","- [Общая диаграмма](#общая-диаграмма)","- [Этап 1: Вход (login)](#этап-1-вход-login)","- [Этап 2: Использование access token](#этап-2-использование-access-token)","- [Этап 3: Ротация токенов (refresh)](#этап-3-ротация-токенов-refresh)","- [Этап 4: Выход (logout)](#этап-4-выход-logout)","- [Этап 5: WebSocket аутентификация](#этап-5-websocket-аутентификация)","- [Этап 6: Доступ к админ-панели](#этап-6-доступ-к-админ-панели)","- [Сравнение токенов](#сравнение-токенов)","","## Общая диаграмма","","```mermaid","flowchart TB","    subgraph Client[\"Клиент (Frontend)\"]","        Login[\"Экран входа\"]","        Storage[\"localStorage\"]","        ApiClient[\"API Client\"]","        WsClient[\"WebSocket Client\"]","    end","","    subgraph Backend[\"Backend (Spring Security)\"]","        AuthController[\"AuthController\"]","        AuthService[\"AuthService\"]","        JwtProvider[\"JwtTokenProvider\"]","        JwtDecoder[\"JwtDecoder\"]","        UserRepo[\"UserAuthRepository\"]","        TokenRepo[\"RefreshTokenRepository\"]","        SecurityFilter[\"Security Filter Chain\"]","        WsInterceptor[\"WebSocketJwtInterceptor\"]","    end","","    subgraph DB[\"БД (PostgreSQL)\"]","        Users[\"users\"]","        RefreshTokens[\"refresh_tokens\"]","    end","","    Login --\u003e|\"POST /auth/login\u003cbr/\u003e{workerCode, password}\"| AuthController","    AuthController --\u003e AuthService","    AuthService --\u003e UserRepo","    UserRepo --\u003e Users","    AuthService --\u003e JwtProvider","    JwtProvider --\u003e|\"accessToken (JWT)\"| AuthController","    JwtProvider --\u003e|\"refreshToken (UUID)\"| AuthService","    AuthService --\u003e TokenRepo","    TokenRepo --\u003e RefreshTokens","    AuthController --\u003e|\"{accessToken, refreshToken}\"| Storage","","    Storage --\u003e|\"Authorization: Bearer \u003caccessToken\u003e\"| ApiClient","    ApiClient --\u003e|\"Запрос к API\"| SecurityFilter","    SecurityFilter --\u003e JwtDecoder","    JwtDecoder --\u003e|\"Валидация подписи,\u003cbr/\u003eissuer, audience, expiry\"| SecurityFilter","","    Storage --\u003e|\"?token=\u003caccessToken\u003e\"| WsClient","    WsClient --\u003e|\"WebSocket handshake\"| WsInterceptor","    WsInterceptor --\u003e JwtProvider","","    ApiClient --\u003e|\"POST /auth/refresh\u003cbr/\u003e{refreshToken}\"| AuthController","    AuthController --\u003e AuthService","    AuthService --\u003e TokenRepo","    TokenRepo --\u003e RefreshTokens","    AuthService --\u003e|\"Новая пара токенов\"| AuthController","    AuthController --\u003e|\"Обновить в Storage\"| Storage","```","","## Этап 1: Вход (login)","","```mermaid","sequenceDiagram","    participant Client as Frontend","    participant Auth as AuthController","    participant Service as AuthService","    participant UserRepo as UserAuthJpaAdapter","    participant TokenRepo as RefreshTokenJpaAdapter","    participant DB as БД","","    Client-\u003e\u003eAuth: POST /auth/login\u003cbr/\u003e{workerCode, password}","    Auth-\u003e\u003eService: authenticate(workerCode, password)","    Service-\u003e\u003eUserRepo: findByCode(workerCode)","    UserRepo-\u003e\u003eDB: SELECT * FROM users WHERE code = ?","    DB--\u003e\u003eUserRepo: UserEntity","    UserRepo--\u003e\u003eService: Optional\u003cUserEntity\u003e","","    alt Пользователь не найден","        Service--\u003e\u003eAuth: AuthenticationException","        Auth--\u003e\u003eClient: 401 Unauthorized","    else Пользователь найден","        Service-\u003e\u003eService: BCrypt.checkpw(password, hash)","        alt Пароль неверный","            Service--\u003e\u003eAuth: AuthenticationException","            Auth--\u003e\u003eClient: 401 Unauthorized","        else Пароль верный","            Service-\u003e\u003eService: generateAccessToken(userId, role)","            Note over Service: JWT HS256, 15 мин\u003cbr/\u003esub=userId, role=ROLE_*","            Service-\u003e\u003eService: generateRefreshToken()","            Note over Service: UUID, 7 дней","            Service-\u003e\u003eService: SHA-256(refreshToken)","            Service-\u003e\u003eTokenRepo: save(tokenHash, userId, expiresAt)","            TokenRepo-\u003e\u003eDB: INSERT INTO refresh_tokens","            TokenRepo--\u003e\u003eService: RefreshTokenEntity","            Service--\u003e\u003eAuth: AuthLoginResponseDTO","            Auth--\u003e\u003eClient: 200 OK\u003cbr/\u003e{accessToken, refreshToken, userId, code, fullName, role}","        end","    end","```","","### Поля ответа login","","| Поле | Описание |","|------|----------|","| `status` | `\"success\"` |","| `userId` | Числовой ID пользователя |","| `code` | Worker code (логин) |","| `fullName` | ФИО сотрудника |","| `role` | Роль (`ADMIN`, `USER`) |","| `accessToken` | JWT для доступа к API |","| `refreshToken` | UUID для обновления пары |","","## Этап 2: Использование access token","","```mermaid","sequenceDiagram","    participant Client as Frontend","    participant API as REST API","    participant Filter as Security Filter","    participant Decoder as JwtDecoder","    participant Converter as JwtAuthenticationConverter","","    Client-\u003e\u003eAPI: GET /api/v1.0.0/workshops/topology\u003cbr/\u003eAuthorization: Bearer \u003caccessToken\u003e","    API-\u003e\u003eFilter: Проверка токена","    Filter-\u003e\u003eDecoder: decode(accessToken)","    Decoder-\u003e\u003eDecoder: Проверка подписи (HS256)","    Decoder-\u003e\u003eDecoder: Проверка issuer (scada-mobile)","    Decoder-\u003e\u003eDecoder: Проверка audience (scada-mobile-api)","    Decoder-\u003e\u003eDecoder: Проверка expiry","    Decoder--\u003e\u003eFilter: Jwt","    Filter-\u003e\u003eConverter: convert(Jwt)","    Converter-\u003e\u003eConverter: Извлечь claim \"role\"","    Converter--\u003e\u003eFilter: JwtAuthenticationToken (ROLE_*)","    Filter--\u003e\u003eAPI: Продолжить обработку","    API--\u003e\u003eClient: 200 OK + данные","```","","## Этап 3: Ротация токенов (refresh)","","```mermaid","sequenceDiagram","    participant Client as Frontend","    participant Auth as AuthController","    participant Service as AuthService","    participant TokenRepo as RefreshTokenRepository","    participant DB as БД","","    Client-\u003e\u003eAuth: POST /auth/refresh\u003cbr/\u003e{refreshToken}","    Auth-\u003e\u003eService: rotate(refreshToken)","    Service-\u003e\u003eService: SHA-256(refreshToken)","    Service-\u003e\u003eTokenRepo: findByTokenHash(hash)","    TokenRepo-\u003e\u003eDB: SELECT * FROM refresh_tokens WHERE token_hash = ?","    DB--\u003e\u003eTokenRepo: RefreshTokenEntity","    TokenRepo--\u003e\u003eService: Optional\u003cRefreshTokenEntity\u003e","","    alt Токен не найден, истек или отозван","        Service--\u003e\u003eAuth: AuthenticationException","        Auth--\u003e\u003eClient: 401 Unauthorized","    else Токен валиден","        Service-\u003e\u003eTokenRepo: revoke(oldTokenId)","        TokenRepo-\u003e\u003eDB: UPDATE refresh_tokens SET revoked = true","        Service-\u003e\u003eService: generateAccessToken(userId, role)","        Service-\u003e\u003eService: generateRefreshToken()","        Service-\u003e\u003eService: SHA-256(newRefreshToken)","        Service-\u003e\u003eTokenRepo: save(newHash, userId, expiresAt)","        TokenRepo-\u003e\u003eDB: INSERT INTO refresh_tokens","        Service--\u003e\u003eAuth: AuthRefreshResponseDTO","        Auth--\u003e\u003eClient: 200 OK\u003cbr/\u003e{accessToken, refreshToken}","    end","```","","### Почему ротация важна","","- При компрометации access token злоумышленник имеет доступ максимум 15 минут.","- При компрометации refresh token он будет отозван при следующем легитимном использовании.","- Каждое обновление генерирует новую пару — старая пара становится недействительной.","","## Этап 4: Выход (logout)","","```mermaid","sequenceDiagram","    participant Client as Frontend","    participant Auth as AuthController","    participant Service as AuthService","    participant TokenRepo as RefreshTokenRepository","    participant DB as БД","","    Client-\u003e\u003eAuth: POST /auth/logout\u003cbr/\u003e{refreshToken}","    Auth-\u003e\u003eService: revoke(refreshToken)","    Service-\u003e\u003eService: SHA-256(refreshToken)","    Service-\u003e\u003eTokenRepo: revokeByHash(hash)","    TokenRepo-\u003e\u003eDB: UPDATE refresh_tokens SET revoked = true","    TokenRepo--\u003e\u003eService: OK","    Service--\u003e\u003eAuth: OK","    Auth--\u003e\u003eClient: 200 OK","    Client-\u003e\u003eClient: Удалить токены из localStorage","```","","## Этап 5: WebSocket аутентификация","","```mermaid","sequenceDiagram","    participant Client as Frontend","    participant Ws as WebSocket Endpoint","    participant Interceptor as WebSocketJwtInterceptor","    participant JwtProvider as JwtTokenProvider","","    Client-\u003e\u003eWs: GET /ws/live?token=\u003caccessToken\u003e","    Ws-\u003e\u003eInterceptor: beforeHandshake(request, response, handler, attributes)","    Interceptor-\u003e\u003eInterceptor: Извлечь token из query param","    Interceptor-\u003e\u003eJwtProvider: validateToken(token)","    JwtProvider--\u003e\u003eInterceptor: userId (или исключение)","","    alt Токен невалиден","        Interceptor--\u003e\u003eWs: false (отклонить handshake)","        Ws--\u003e\u003eClient: 401 Unauthorized","    else Токен валиден","        Interceptor-\u003e\u003eInterceptor: attributes.put(\"userId\", userId)","        Interceptor--\u003e\u003eWs: true (продолжить handshake)","        Ws--\u003e\u003eClient: 101 Switching Protocols","    end","```","","## Этап 6: Доступ к админ-панели","","```mermaid","sequenceDiagram","    participant Client as Frontend","    participant Admin as AdminUserController","    participant Security as Spring Security","","    Client-\u003e\u003eAdmin: GET /api/v1.0.0/admin/users/list\u003cbr/\u003eAuthorization: Bearer \u003ctoken\u003e","    Admin-\u003e\u003eSecurity: @PreAuthorize(\"hasRole('ADMIN')\")","    Security-\u003e\u003eSecurity: Извлечь роль из JWT","","    alt Роль != ADMIN","        Security--\u003e\u003eAdmin: AccessDeniedException","        Admin--\u003e\u003eClient: 403 Forbidden","    else Роль == ADMIN","        Security--\u003e\u003eAdmin: Продолжить","        Admin--\u003e\u003eClient: 200 OK + список пользователей","    end","```","","## Сравнение токенов","","| Характеристика | Access Token | Refresh Token |","|----------------|--------------|---------------|","| Формат | JWT (HS256) | UUID (plain text) |","| Срок действия | 15 минут | 7 дней |","| Хранение на клиенте | localStorage | localStorage |","| Хранение на сервере | Не хранится | SHA-256 хеш в БД |","| Назначение | Доступ к API | Получение новой пары |","| Передача | Header `Authorization: Bearer` | Body JSON |","| Отзыв | Невозможен (stateless) | Возможен (флаг `revoked`) |","| Ротация | При каждом refresh | При каждом refresh |"],"stylingDirectives":[[[0,37,"pl-mh"],[2,37,"pl-en"]],[],[[0,10,"pl-mh"],[3,10,"pl-en"]],[],[],[[0,20,"pl-mh"],[3,20,"pl-en"]],[[0,1,"pl-v"],[2,3,"pl-s"],[10,11,"pl-s"],[11,12,"pl-s"],[12,20,"pl-corl"],[20,21,"pl-s"]],[[0,1,"pl-v"],[2,3,"pl-s"],[18,19,"pl-s"],[19,20,"pl-s"],[20,36,"pl-corl"],[36,37,"pl-s"]],[[0,1,"pl-v"],[2,3,"pl-s"],[23,24,"pl-s"],[24,25,"pl-s"],[25,43,"pl-corl"],[43,44,"pl-s"]],[[0,1,"pl-v"],[2,3,"pl-s"],[37,38,"pl-s"],[38,39,"pl-s"],[39,73,"pl-corl"],[73,74,"pl-s"]],[[0,1,"pl-v"],[2,3,"pl-s"],[36,37,"pl-s"],[37,38,"pl-s"],[38,69,"pl-corl"],[69,70,"pl-s"]],[[0,1,"pl-v"],[2,3,"pl-s"],[25,26,"pl-s"],[26,27,"pl-s"],[27,47,"pl-corl"],[47,48,"pl-s"]],[[0,1,"pl-v"],[2,3,"pl-s"],[35,36,"pl-s"],[36,37,"pl-s"],[37,69,"pl-corl"],[69,70,"pl-s"]],[[0,1,"pl-v"],[2,3,"pl-s"],[32,33,"pl-s"],[33,34,"pl-s"],[34,63,"pl-corl"],[63,64,"pl-s"]],[[0,1,"pl-v"],[2,3,"pl-s"],[20,21,"pl-s"],[21,22,"pl-s"],[22,40,"pl-corl"],[40,41,"pl-s"]],[],[[0,18,"pl-mh"],[3,18,"pl-en"]],[],[[0,3,"pl-s"],[3,10,"pl-en"],[10,10,"pl-c1"]],[[0,12,"pl-c1"]],[[0,40,"pl-c1"]],[[0,28,"pl-c1"]],[[0,31,"pl-c1"]],[[0,31,"pl-c1"]],[[0,36,"pl-c1"]],[[0,7,"pl-c1"]],[[0,0,"pl-c1"]],[[0,49,"pl-c1"]],[[0,40,"pl-c1"]],[[0,34,"pl-c1"]],[[0,39,"pl-c1"]],[[0,32,"pl-c1"]],[[0,38,"pl-c1"]],[[0,43,"pl-c1"]],[[0,47,"pl-c1"]],[[0,48,"pl-c1"]],[[0,7,"pl-c1"]],[[0,0,"pl-c1"]],[[0,34,"pl-c1"]],[[0,22,"pl-c1"]],[[0,39,"pl-c1"]],[[0,7,"pl-c1"]],[[0,0,"pl-c1"]],[[0,75,"pl-c1"]],[[0,34,"pl-c1"]],[[0,28,"pl-c1"]],[[0,22,"pl-c1"]],[[0,31,"pl-c1"]],[[0,55,"pl-c1"]],[[0,54,"pl-c1"]],[[0,29,"pl-c1"]],[[0,31,"pl-c1"]],[[0,61,"pl-c1"]],[[0,0,"pl-c1"]],[[0,64,"pl-c1"]],[[0,48,"pl-c1"]],[[0,33,"pl-c1"]],[[0,84,"pl-c1"]],[[0,0,"pl-c1"]],[[0,48,"pl-c1"]],[[0,53,"pl-c1"]],[[0,33,"pl-c1"]],[[0,0,"pl-c1"]],[[0,73,"pl-c1"]],[[0,34,"pl-c1"]],[[0,29,"pl-c1"]],[[0,31,"pl-c1"]],[[0,56,"pl-c1"]],[[0,52,"pl-c1"]],[[0,0,"pl-c1"],[0,3,"pl-s"]],[],[[0,23,"pl-mh"],[3,23,"pl-en"]],[],[[0,3,"pl-s"],[3,10,"pl-en"],[10,10,"pl-c1"]],[[0,15,"pl-c1"]],[[0,34,"pl-c1"]],[[0,38,"pl-c1"]],[[0,38,"pl-c1"]],[[0,46,"pl-c1"]],[[0,51,"pl-c1"]],[[0,24,"pl-c1"]],[[0,0,"pl-c1"]],[[0,62,"pl-c1"]],[[0,54,"pl-c1"]],[[0,46,"pl-c1"]],[[0,53,"pl-c1"]],[[0,30,"pl-c1"]],[[0,45,"pl-c1"]],[[0,0,"pl-c1"]],[[0,30,"pl-c1"]],[[0,48,"pl-c1"]],[[0,40,"pl-c1"]],[[0,28,"pl-c1"]],[[0,57,"pl-c1"]],[[0,27,"pl-c1"]],[[0,52,"pl-c1"]],[[0,44,"pl-c1"]],[[0,26,"pl-c1"]],[[0,64,"pl-c1"]],[[0,76,"pl-c1"]],[[0,53,"pl-c1"]],[[0,43,"pl-c1"]],[[0,52,"pl-c1"]],[[0,67,"pl-c1"]],[[0,54,"pl-c1"]],[[0,52,"pl-c1"]],[[0,49,"pl-c1"]],[[0,96,"pl-c1"]],[[0,11,"pl-c1"]],[[0,7,"pl-c1"]],[[0,0,"pl-c1"],[0,3,"pl-s"]],[],[[0,21,"pl-mh"],[4,21,"pl-en"]],[],[[0,1,"pl-ml"],[7,8,"pl-ml"],[18,19,"pl-ml"]],[[0,1,"pl-ml"],[7,8,"pl-ml"],[18,19,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,9,"pl-c1"],[9,10,"pl-s"],[11,12,"pl-ml"],[13,14,"pl-s"],[14,23,"pl-c1"],[23,24,"pl-s"],[25,26,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,9,"pl-c1"],[9,10,"pl-s"],[11,12,"pl-ml"],[38,39,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,7,"pl-c1"],[7,8,"pl-s"],[9,10,"pl-ml"],[31,32,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,11,"pl-c1"],[11,12,"pl-s"],[13,14,"pl-ml"],[30,31,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,7,"pl-c1"],[7,8,"pl-s"],[9,10,"pl-ml"],[17,18,"pl-s"],[18,23,"pl-c1"],[23,24,"pl-s"],[26,27,"pl-s"],[27,31,"pl-c1"],[31,32,"pl-s"],[34,35,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,14,"pl-c1"],[14,15,"pl-s"],[16,17,"pl-ml"],[40,41,"pl-ml"]],[[0,1,"pl-ml"],[2,3,"pl-s"],[3,15,"pl-c1"],[15,16,"pl-s"],[17,18,"pl-ml"],[44,45,"pl-ml"]],[],[[0,37,"pl-mh"],[3,37,"pl-en"]],[],[[0,3,"pl-s"],[3,10,"pl-en"],[10,10,"pl-c1"]],[[0,15,"pl-c1"]],[[0,34,"pl-c1"]],[[0,31,"pl-c1"]],[[0,41,"pl-c1"]],[[0,37,"pl-c1"]],[[0,55,"pl-c1"]],[[0,0,"pl-c1"]],[[0,92,"pl-c1"]],[[0,33,"pl-c1"]],[[0,41,"pl-c1"]],[[0,47,"pl-c1"]],[[0,53,"pl-c1"]],[[0,59,"pl-c1"]],[[0,38,"pl-c1"]],[[0,26,"pl-c1"]],[[0,36,"pl-c1"]],[[0,47,"pl-c1"]],[[0,56,"pl-c1"]],[[0,39,"pl-c1"]],[[0,34,"pl-c1"]],[[0,0,"pl-c1"],[0,3,"pl-s"]],[],[[0,36,"pl-mh"],[3,36,"pl-en"]],[],[[0,3,"pl-s"],[3,10,"pl-en"],[10,10,"pl-c1"]],[[0,15,"pl-c1"]],[[0,34,"pl-c1"]],[[0,38,"pl-c1"]],[[0,38,"pl-c1"]],[[0,51,"pl-c1"]],[[0,24,"pl-c1"]],[[0,0,"pl-c1"]],[[0,56,"pl-c1"]],[[0,40,"pl-c1"]],[[0,44,"pl-c1"]],[[0,46,"pl-c1"]],[[0,69,"pl-c1"]],[[0,39,"pl-c1"]],[[0,54,"pl-c1"]],[[0,0,"pl-c1"]],[[0,42,"pl-c1"]],[[0,48,"pl-c1"]],[[0,40,"pl-c1"]],[[0,22,"pl-c1"]],[[0,47,"pl-c1"]],[[0,64,"pl-c1"]],[[0,60,"pl-c1"]],[[0,49,"pl-c1"]],[[0,51,"pl-c1"]],[[0,61,"pl-c1"]],[[0,50,"pl-c1"]],[[0,47,"pl-c1"]],[[0,62,"pl-c1"]],[[0,7,"pl-c1"]],[[0,0,"pl-c1"],[0,3,"pl-s"]],[],[[0,24,"pl-mh"],[4,24,"pl-en"]],[],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[[0,1,"pl-v"]],[],[[0,25,"pl-mh"],[3,25,"pl-en"]],[],[[0,3,"pl-s"],[3,10,"pl-en"],[10,10,"pl-c1"]],[[0,15,"pl-c1"]],[[0,34,"pl-c1"]],[[0,38,"pl-c1"]],[[0,38,"pl-c1"]],[[0,51,"pl-c1"]],[[0,24,"pl-c1"]],[[0,0,"pl-c1"]],[[0,55,"pl-c1"]],[[0,40,"pl-c1"]],[[0,44,"pl-c1"]],[[0,43,"pl-c1"]],[[0,60,"pl-c1"]],[[0,28,"pl-c1"]],[[0,23,"pl-c1"]],[[0,26,"pl-c1"]],[[0,51,"pl-c1"]],[[0,0,"pl-c1"],[0,3,"pl-s"]],[],[[0,35,"pl-mh"],[3,35,"pl-en"]],[],[[0,3,"pl-s"],[3,10,"pl-en"],[10,10,"pl-c1"]],[[0,15,"pl-c1"]],[[0,34,"pl-c1"]],[[0,40,"pl-c1"]],[[0,54,"pl-c1"]],[[0,47,"pl-c1"]],[[0,0,"pl-c1"]],[[0,49,"pl-c1"]],[[0,77,"pl-c1"]],[[0,59,"pl-c1"]],[[0,51,"pl-c1"]],[[0,55,"pl-c1"]],[[0,0,"pl-c1"]],[[0,23,"pl-c1"]],[[0,54,"pl-c1"]],[[0,38,"pl-c1"]],[[0,22,"pl-c1"]],[[0,67,"pl-c1"]],[[0,54,"pl-c1"]],[[0,45,"pl-c1"]],[[0,7,"pl-c1"]],[[0,0,"pl-c1"],[0,3,"pl-s"]],[],[[0,32,"pl-mh"],[3,32,"pl-en"]],[],[[0,3,"pl-s"],[3,10,"pl-en"],[10,10,"pl-c1"]],[[0,15,"pl-c1"]],[[0,34,"pl-c1"]],[[0,44,"pl-c1"]],[[0,43,"pl-c1"]],[[0,0,"pl-c1"]],[[0,86,"pl-c1"]],[[0,55,"pl-c1"]],[[0,44,"pl-c1"]],[[0,0,"pl-c1"]],[[0,21,"pl-c1"]],[[0,48,"pl-c1"]],[[0,38,"pl-c1"]],[[0,22,"pl-c1"]],[[0,37,"pl-c1"]],[[0,54,"pl-c1"]],[[0,7,"pl-c1"]],[[0,0,"pl-c1"],[0,3,"pl-s"]],[],[[0,20,"pl-mh"],[3,20,"pl-en"]],[],[[0,1,"pl-ml"],[17,18,"pl-ml"],[32,33,"pl-ml"],[48,49,"pl-ml"]],[[0,1,"pl-ml"],[17,18,"pl-ml"],[32,33,"pl-ml"],[48,49,"pl-ml"]],[[0,1,"pl-ml"],[9,10,"pl-ml"],[23,24,"pl-ml"],[43,44,"pl-ml"]],[[0,1,"pl-ml"],[16,17,"pl-ml"],[27,28,"pl-ml"],[36,37,"pl-ml"]],[[0,1,"pl-ml"],[22,23,"pl-ml"],[37,38,"pl-ml"],[52,53,"pl-ml"]],[[0,1,"pl-ml"],[22,23,"pl-ml"],[36,37,"pl-ml"],[55,56,"pl-ml"]],[[0,1,"pl-ml"],[13,14,"pl-ml"],[28,29,"pl-ml"],[51,52,"pl-ml"]],[[0,1,"pl-ml"],[11,12,"pl-ml"],[20,21,"pl-s"],[21,42,"pl-c1"],[42,43,"pl-s"],[44,45,"pl-ml"],[56,57,"pl-ml"]],[[0,1,"pl-ml"],[8,9,"pl-ml"],[33,34,"pl-ml"],[50,51,"pl-s"],[51,58,"pl-c1"],[58,59,"pl-s"],[61,62,"pl-ml"]],[[0,1,"pl-ml"],[10,11,"pl-ml"],[31,32,"pl-ml"],[52,53,"pl-ml"]]],"colorizedLines":null}},"title":"scada-mobile/AUTH_FLOW.md at main · gleb7499/scada-mobile","appPayload":{},"meta":{"title":"scada-mobile/AUTH_FLOW.md at main · gleb7499/scada-mobile"}}</script>
  <div data-target="react-app.reactRoot"><meta name="github-code-view-meta-stats" id="github-code-view-meta-stats" data-hydrostats="publish"/> <!-- --> <a hidden="" id="code-view-repo-link" href="/gleb7499/scada-mobile" data-discover="true"></a> <div class="d-none"></div><div><div style="--spacing:var(--spacing-none)" class="prc-PageLayout-PageLayoutRoot--KH-d" data-component="SplitPageLayout" data-has-sidebar="true"><div class="prc-PageLayout-SidebarWrapper-kLG4B CopilotSidePanelSidebar-module__SidePanel__L3O0C CopilotSidePanelSidebar-module__HiddenSidePanel__TBRGn" style="--spacing-column:var(--spacing-none)" data-is-hidden="false" data-position="end" data-sticky="true" data-responsive-variant="fullscreen"><div class="prc-PageLayout-VerticalDivider-9QRmK prc-PageLayout-SidebarVerticalDivider-0Rl0V" data-component="PageLayout.VerticalDivider" data-variant="line" data-position="end" style="--spacing:var(--spacing-none)"><div class="prc-PageLayout-DraggableHandle-9s6B4" data-component="PageLayout.DragHandle" role="slider" aria-label="Draggable pane splitter" aria-valuemin="450" aria-valuemax="768" aria-valuenow="544" aria-valuetext="Pane width 544 pixels" tabindex="0"></div></div><div class="prc-PageLayout-Sidebar-iciWg" data-component="SplitPageLayout.Sidebar" data-resizable="true" style="--spacing:var(--spacing-normal);--pane-min-width:450px;--pane-max-width:768px;--pane-width-custom:544px;--pane-width-size:var(--pane-width-custom);--pane-width:544px"><div class="height-full" data-testid="copilot-code-view-side-panel"><div id="copilot-side-panel-content" class="height-full"></div></div></div></div><div class="prc-PageLayout-PageLayoutWrapper-2BhU2" data-width="full"><div class="prc-PageLayout-PageLayoutContent-BneH9"><div id="repos-file-tree-sidebar" class="CodeViewFileTreeLayout-module__sidebar__n_Aau" tabindex="0"><div class="prc-PageLayout-PaneWrapper-pHPop ReposFileTreePane-module__Pane__rBZpI ReposFileTreePane-module__HideTree__AYZnm ReposFileTreePane-module__HidePane__VHAVt" style="--offset-header:0px;--spacing-row:var(--spacing-none);--spacing-column:var(--spacing-none)" data-is-hidden="false" data-position="start" data-sticky="true"><div class="prc-PageLayout-HorizontalDivider-JLVqp prc-PageLayout-PaneHorizontalDivider-9tbnE" data-component="PageLayout.HorizontalDivider" data-variant-regular="none" data-variant-narrow="none" data-position="start" style="--spacing-divider:var(--spacing-none);--spacing:var(--spacing-none)"></div><div class="prc-PageLayout-Pane-AyzHK" data-component="SplitPageLayout.Pane" data-resizable="true" style="--spacing:var(--spacing-none);--pane-min-width:256px;--pane-max-width:calc(100vw - var(--pane-max-width-diff));--pane-width-size:var(--pane-width-large);--pane-width:320px"></div><div class="prc-PageLayout-VerticalDivider-9QRmK prc-PageLayout-PaneVerticalDivider-le57g" data-component="PageLayout.VerticalDivider" data-variant-narrow="none" data-variant-regular="line" data-variant-wide="line" data-position="start" style="--spacing:var(--spacing-none)"><div class="prc-PageLayout-DraggableHandle-9s6B4" data-component="PageLayout.DragHandle" role="slider" aria-label="Draggable pane splitter" aria-valuemin="256" aria-valuemax="600" aria-valuenow="320" aria-valuetext="Pane width 320 pixels" tabindex="0"></div></div></div></div><div data-component="SplitPageLayout.Content" class="prc-PageLayout-ContentWrapper-gR9eG"><div class="prc-PageLayout-Content-xWL-A" data-width="full" style="--spacing:var(--spacing-none)"><div class="SharedPageLayout-module__content__IwGAp" data-selector="repos-split-pane-content" id="repos-split-pane-content" tabindex="0"> <!-- --> <div class="container CodeViewHeader-module__Box__JkPOb"><div class="CodeViewHeader-module__StickyHeader__Qn7UN" id="StickyHeader"><div class="CodeViewHeader-module__Box_1__SbNDV"><div class="CodeViewHeader-module__Box_2__TB46f"><div class="react-code-view-header-wrap--narrow CodeViewHeader-module__Box_3__q1zUL"><div class="CodeViewHeader-module__treeToggleWrapper__RQ__9"><h2 class="use-tree-pane-module__Heading__s4QbZ prc-Heading-Heading-MtWFE" data-component="Heading"><button data-component="Button" type="button" aria-label="Expand file tree" data-testid="expand-file-tree-button-mobile" class="prc-Button-ButtonBase-9n-Xk ExpandFileTreeButton-module__Button_1__Svs95" data-loading="false" data-size="medium" data-variant="invisible"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-arrow-left" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M7.78 12.53a.75.75 0 0 1-1.06 0L2.47 8.28a.75.75 0 0 1 0-1.06l4.25-4.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042L4.81 7h7.44a.75.75 0 0 1 0 1.5H4.81l2.97 2.97a.75.75 0 0 1 0 1.06Z"></path></svg></span><span data-component="text" class="prc-Button-Label-FWkx3">Files</span></span></button><button data-component="IconButton" type="button" data-testid="expand-file-tree-button" aria-controls="repos-file-tree" class="prc-Button-ButtonBase-9n-Xk position-relative ExpandFileTreeButton-module__expandButton__hDOcv ExpandFileTreeButton-module__filesButtonBreakpoint__zEvz3 fgColor-muted prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="medium" data-variant="invisible" aria-labelledby="_R_4lla9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-sidebar-collapse" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M6.823 7.823a.25.25 0 0 1 0 .354l-2.396 2.396A.25.25 0 0 1 4 10.396V5.604a.25.25 0 0 1 .427-.177Z"></path><path d="M1.75 0h12.5C15.216 0 16 .784 16 1.75v12.5A1.75 1.75 0 0 1 14.25 16H1.75A1.75 1.75 0 0 1 0 14.25V1.75C0 .784.784 0 1.75 0ZM1.5 1.75v12.5c0 .138.112.25.25.25H9.5v-13H1.75a.25.25 0 0 0-.25.25ZM11 14.5h3.25a.25.25 0 0 0 .25-.25V1.75a.25.25 0 0 0-.25-.25H11Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="se" data-component="Tooltip" aria-hidden="true" id="_R_4lla9lik5_">Expand file tree</span><div class="d-none"></div></h2></div><div class="react-code-view-header-mb--narrow mr-2"><button data-component="Button" type="button" aria-haspopup="true" aria-expanded="false" tabindex="0" aria-label="main branch" data-testid="anchor-button" data-icv-name="Switch branches/tags" class="prc-Button-ButtonBase-9n-Xk ref-selector-class RefSelectorAnchoredOverlay-module__RefSelectorOverlayBtn__a3WK3" data-loading="false" data-size="medium" data-variant="default" id="ref-picker-repos-header-ref-selector-wide"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-git-branch" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M9.5 3.25a2.25 2.25 0 1 1 3 2.122V6A2.5 2.5 0 0 1 10 8.5H6a1 1 0 0 0-1 1v1.128a2.251 2.251 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.5 0v1.836A2.493 2.493 0 0 1 6 7h4a1 1 0 0 0 1-1v-.628A2.25 2.25 0 0 1 9.5 3.25Zm-6 0a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Zm8.25-.75a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5ZM4.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z"></path></svg></span><span data-component="text" class="prc-Button-Label-FWkx3"><div class="RefSelectorAnchoredOverlay-module__RefSelectorOverlayContainer__yaf4p"><div style="max-width:125px" class="ref-selector-button-text-container RefSelectorAnchoredOverlay-module__RefSelectorBtnTextContainer__Di3rk"><span class="RefSelectorAnchoredOverlay-module__RefSelectorText__w_fmP">main</span></div></div></span><span data-component="trailingVisual" class="prc-Button-Visual-YNt2F prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-down" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m4.427 7.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.396 7H4.604a.25.25 0 0 0-.177.427Z"></path></svg></span></span></button><div class="d-none"></div></div><div class="react-code-view-header-mb--narrow CodeViewHeader-module__Box_5__MQ0hL"><div class="Breadcrumb-module__container__Vxvev Breadcrumb-module__lg__Rjz0A"><nav data-testid="breadcrumbs" aria-labelledby="repos-header-breadcrumb-heading" id="repos-header-breadcrumb" class="Breadcrumb-module__nav__rQFDj"><h2 class="sr-only ScreenReaderHeading-module__userSelectNone__rwWIk prc-Heading-Heading-MtWFE" data-component="Heading" data-testid="screen-reader-heading" id="repos-header-breadcrumb-heading">Breadcrumbs</h2><ol class="Breadcrumb-module__list__ZH6zr"><li class="Breadcrumb-module__listItem__Ib0x_"><a class="Breadcrumb-module__repoLink__O2Nbs prc-Link-Link-9ZwDx" data-component="Link" data-testid="breadcrumbs-repo-link" href="/gleb7499/scada-mobile/tree/main" data-discover="true">scada-mobile</a></li></ol></nav><div data-testid="breadcrumbs-filename" class="Breadcrumb-module__filename__equZR"><span class="Breadcrumb-module__separator__eNwsI Breadcrumb-module__lg__Rjz0A" aria-hidden="true">/</span><h1 class="Breadcrumb-module__filenameHeading__MNMtw Breadcrumb-module__lg__Rjz0A prc-Heading-Heading-MtWFE" data-component="Heading" tabindex="-1" id="file-name-id">AUTH_FLOW.md</h1></div><button data-component="IconButton" type="button" class="prc-Button-ButtonBase-9n-Xk ml-2 prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="invisible" aria-labelledby="_R_7lla9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-copy" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg></button><span class="CopyToClipboardIconButton-module__tooltip__WyiwL prc-TooltipV2-Tooltip-tLeuB" data-direction="nw" data-component="Tooltip" aria-label="Copy path" aria-hidden="true" id="_R_7lla9lik5_">Copy path</span></div></div></div><div class="react-code-view-header-element--wide"><div class="CodeViewHeader-module__Box_7___0R6c"><div class="d-flex gap-2"><div><div class="CodeViewHeader-module__FileResultsList__JDzUy"><span class="d-flex FileResultsList-module__FilesSearchBox__ivVkc TextInput-wrapper prc-components-TextInputWrapper-Hpdqi prc-components-TextInputBaseWrapper-wY-n0" data-no-trailing-action="true" data-component="TextInput" data-leading-visual="true" data-trailing-visual="true" aria-busy="false"><span class="TextInput-icon" id="_R_1cpla9lik5_" aria-hidden="true" data-component="TextInput.LeadingVisual"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-search" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215ZM11.5 7a4.499 4.499 0 1 0-8.997 0A4.499 4.499 0 0 0 11.5 7Z"></path></svg></span><input type="text" aria-label="Go to file" role="combobox" aria-controls="file-results-list" aria-expanded="false" aria-haspopup="dialog" autoCorrect="off" spellCheck="false" placeholder="Go to file" aria-describedby="_R_1cpla9lik5_ _R_1cpla9lik5H1_" data-component="input" class="prc-components-Input-IwWrt" value=""/><span class="TextInput-icon" id="_R_1cpla9lik5H1_" aria-hidden="true" data-component="TextInput.TrailingVisual"></span></span></div><div class="d-none"></div></div><button data-component="Button" type="button" style="display:none" class="prc-Button-ButtonBase-9n-Xk NavigationMenu-module__Button__LpKgm" data-loading="false" data-no-visuals="true" data-size="medium" data-variant="default"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="text" class="prc-Button-Label-FWkx3">Blame</span></span></button><div class="d-none"></div><button data-component="IconButton" type="button" data-testid="more-file-actions-button-nav-menu-wide" aria-haspopup="true" aria-expanded="false" tabindex="0" class="prc-Button-ButtonBase-9n-Xk js-blob-dropdown-click NavigationMenu-module__IconButton__HpX3G prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="medium" data-variant="default" aria-labelledby="_R_7p9la9lik5_" id="_R_99la9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-kebab-horizontal" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM1.5 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm13 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="nw" data-component="Tooltip" aria-hidden="true" id="_R_7p9la9lik5_">More file actions</span></div></div></div><div class="react-code-view-header-element--narrow"><div class="CodeViewHeader-module__Box_7___0R6c"><div class="d-flex gap-2"><button data-component="Button" type="button" style="display:none" class="prc-Button-ButtonBase-9n-Xk NavigationMenu-module__Button__LpKgm" data-loading="false" data-no-visuals="true" data-size="medium" data-variant="default"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="text" class="prc-Button-Label-FWkx3">Blame</span></span></button><div class="d-none"></div><button data-component="IconButton" type="button" data-testid="more-file-actions-button-nav-menu-narrow" aria-haspopup="true" aria-expanded="false" tabindex="0" class="prc-Button-ButtonBase-9n-Xk js-blob-dropdown-click NavigationMenu-module__IconButton__HpX3G prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="medium" data-variant="default" aria-labelledby="_R_7pdla9lik5_" id="_R_9dla9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-kebab-horizontal" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM1.5 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm13 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="nw" data-component="Tooltip" aria-hidden="true" id="_R_7pdla9lik5_">More file actions</span></div></div></div></div></div></div></div><div class="CodeView-module__contentWrapper__cG2JH"><div class="react-code-view-bottom-padding"><div class="BlobTopBanners-module__Box__v_nvx"></div></div> <div class="d-none"></div><div class="d-flex flex-column border rounded-2 tmp-mb-3 pl-1"><div class="LatestCommit-module__Box__B25ZT"><h2 class="sr-only ScreenReaderHeading-module__userSelectNone__rwWIk prc-Heading-Heading-MtWFE" data-component="Heading" data-testid="screen-reader-heading">Latest commit</h2><div style="width:120px" class="Skeleton Skeleton--text" data-testid="loading"> </div><div class="d-flex flex-shrink-0 gap-2"><div data-testid="latest-commit-details" class="d-none d-sm-flex flex-items-center"></div><div class="d-flex gap-2"><h2 class="sr-only ScreenReaderHeading-module__userSelectNone__rwWIk prc-Heading-Heading-MtWFE" data-component="Heading" data-testid="screen-reader-heading">History</h2><a data-component="LinkButton" href="/gleb7499/scada-mobile/commits/main/AUTH_FLOW.md" class="prc-Button-ButtonBase-9n-Xk d-none d-lg-flex LinkButton-module__linkButton__nFnov flex-items-center fgColor-default" data-loading="false" data-size="small" data-variant="invisible"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-history" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m.427 1.927 1.215 1.215a8.002 8.002 0 1 1-1.6 5.685.75.75 0 1 1 1.493-.154 6.5 6.5 0 1 0 1.18-4.458l1.358 1.358A.25.25 0 0 1 3.896 6H.25A.25.25 0 0 1 0 5.75V2.104a.25.25 0 0 1 .427-.177ZM7.75 4a.75.75 0 0 1 .75.75v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5A.75.75 0 0 1 7.75 4Z"></path></svg></span><span data-component="text" class="prc-Button-Label-FWkx3"><span class="fgColor-default">History</span></span></span></a><div class="d-sm-none"></div><div class="d-flex d-lg-none"><a data-component="LinkButton" aria-label="View commit history for this file." href="/gleb7499/scada-mobile/commits/main/AUTH_FLOW.md" class="prc-Button-ButtonBase-9n-Xk LinkButton-module__linkButton__nFnov flex-items-center fgColor-default" data-loading="false" data-size="small" data-variant="invisible" aria-describedby="_R_4mlala9lik5_"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-history" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m.427 1.927 1.215 1.215a8.002 8.002 0 1 1-1.6 5.685.75.75 0 1 1 1.493-.154 6.5 6.5 0 1 0 1.18-4.458l1.358 1.358A.25.25 0 0 1 3.896 6H.25A.25.25 0 0 1 0 5.75V2.104a.25.25 0 0 1 .427-.177ZM7.75 4a.75.75 0 0 1 .75.75v2.992l2.028.812a.75.75 0 0 1-.557 1.392l-2.5-1A.751.751 0 0 1 7 8.25v-3.5A.75.75 0 0 1 7.75 4Z"></path></svg></span></span></a><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="s" data-component="Tooltip" role="tooltip" aria-hidden="true" id="_R_4mlala9lik5_">History</span></div></div></div></div></div><div class="d-flex flex-row"><div class="container BlobViewContent-module__blobContainer__DtH2d"><div class="react-code-size-details-banner BlobViewContent-module__codeSizeDetails__e5sUw"><div class="react-code-size-details-banner CodeSizeDetails-module__Box__VcD6l"><div class="text-mono CodeSizeDetails-module__Box_1__GVxQL"><div data-testid="blob-size" class="CodeSizeDetails-module__Truncate_1__lE93V prc-Truncate-Truncate-2G1eo" data-inline="true" title="10.6 KB" style="--truncate-max-width:100%"><span>268 lines (230 loc) · 10.6 KB</span></div></div></div></div><div class="react-blob-view-header-sticky BlobViewContent-module__stickyHeader__VwxB5" id="repos-sticky-header"><div class="BlobViewHeader-module__Box__yhm9u"><div class="react-blob-sticky-header"><div class="FileNameStickyHeader-module__outerWrapper__ZL4Xc FileNameStickyHeader-module__outerWrapperHidden__Zpynk"><div class="FileNameStickyHeader-module__Box_1__Hazu5"><div class="FileNameStickyHeader-module__Box_2__hoolP"><div class="FileNameStickyHeader-module__Box_3__MVKsk"><button data-component="Button" type="button" aria-haspopup="true" aria-expanded="false" tabindex="0" aria-label="main branch" data-testid="anchor-button" data-icv-name="Switch branches/tags" class="prc-Button-ButtonBase-9n-Xk ref-selector-class RefSelectorAnchoredOverlay-module__RefSelectorOverlayBtn__a3WK3" data-loading="false" data-size="medium" data-variant="default" id="ref-picker-repos-header-ref-selector"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-git-branch" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M9.5 3.25a2.25 2.25 0 1 1 3 2.122V6A2.5 2.5 0 0 1 10 8.5H6a1 1 0 0 0-1 1v1.128a2.251 2.251 0 1 1-1.5 0V5.372a2.25 2.25 0 1 1 1.5 0v1.836A2.493 2.493 0 0 1 6 7h4a1 1 0 0 0 1-1v-.628A2.25 2.25 0 0 1 9.5 3.25Zm-6 0a.75.75 0 1 0 1.5 0 .75.75 0 0 0-1.5 0Zm8.25-.75a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5ZM4.25 12a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5Z"></path></svg></span><span data-component="text" class="prc-Button-Label-FWkx3"><div class="RefSelectorAnchoredOverlay-module__RefSelectorOverlayContainer__yaf4p"><div style="max-width:125px" class="ref-selector-button-text-container RefSelectorAnchoredOverlay-module__RefSelectorBtnTextContainer__Di3rk"><span class="RefSelectorAnchoredOverlay-module__RefSelectorText__w_fmP">main</span></div></div></span><span data-component="trailingVisual" class="prc-Button-Visual-YNt2F prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-triangle-down" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="m4.427 7.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.396 7H4.604a.25.25 0 0 0-.177.427Z"></path></svg></span></span></button><div class="d-none"></div></div><div class="FileNameStickyHeader-module__Box_4__FLhtt"><div class="Breadcrumb-module__container__Vxvev Breadcrumb-module__md__Wb1Gs"><nav data-testid="breadcrumbs" aria-labelledby="sticky-breadcrumb-heading" id="sticky-breadcrumb" class="Breadcrumb-module__nav__rQFDj"><h2 class="sr-only ScreenReaderHeading-module__userSelectNone__rwWIk prc-Heading-Heading-MtWFE" data-component="Heading" data-testid="screen-reader-heading" id="sticky-breadcrumb-heading">Breadcrumbs</h2><ol class="Breadcrumb-module__list__ZH6zr"><li class="Breadcrumb-module__listItem__Ib0x_"><a class="Breadcrumb-module__repoLink__O2Nbs prc-Link-Link-9ZwDx" data-component="Link" data-testid="breadcrumbs-repo-link" href="/gleb7499/scada-mobile/tree/main" data-discover="true">scada-mobile</a></li></ol></nav><div data-testid="breadcrumbs-filename" class="Breadcrumb-module__filename__equZR"><span class="Breadcrumb-module__separator__eNwsI Breadcrumb-module__md__Wb1Gs" aria-hidden="true">/</span><h1 class="Breadcrumb-module__filenameHeading__MNMtw Breadcrumb-module__md__Wb1Gs prc-Heading-Heading-MtWFE" data-component="Heading" tabindex="-1" id="sticky-file-name-id">AUTH_FLOW.md</h1></div><button data-component="IconButton" type="button" class="prc-Button-ButtonBase-9n-Xk ml-2 prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="invisible" aria-labelledby="_R_7lcpala9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-copy" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg></button><span class="CopyToClipboardIconButton-module__tooltip__WyiwL prc-TooltipV2-Tooltip-tLeuB" data-direction="s" data-component="Tooltip" aria-label="Copy path" aria-hidden="true" id="_R_7lcpala9lik5_">Copy path</span></div></div></div><button data-component="Button" type="button" class="prc-Button-ButtonBase-9n-Xk FileNameStickyHeader-module__Button__LSEU_ FileNameStickyHeader-module__GoToTopButton__nxAFn" data-loading="false" data-size="small" data-variant="invisible"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="leadingVisual" class="prc-Button-Visual-YNt2F prc-Button-LeadingVisual-UySKu prc-Button-VisualWrap-E4cnq"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-arrow-up" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M3.47 7.78a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0l4.25 4.25a.751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018L9 4.81v7.44a.75.75 0 0 1-1.5 0V4.81L4.53 7.78a.75.75 0 0 1-1.06 0Z"></path></svg></span><span data-component="text" class="prc-Button-Label-FWkx3">Top</span></span></button></div></div></div><div class="BlobViewHeader-module__Box_1__VEmuQ"><h2 class="sr-only ScreenReaderHeading-module__userSelectNone__rwWIk prc-Heading-Heading-MtWFE" data-component="Heading" data-testid="screen-reader-heading">File metadata and controls</h2><div class="BlobViewHeader-module__Box_2__icUs2"><ul aria-label="File view" class="prc-SegmentedControl-SegmentedControl-lqIXp BlobTabButtons-module__SegmentedControl__jen2u" data-variant="default" data-size="small" data-component="SegmentedControl"><li class="prc-SegmentedControl-Item-tSCQh" data-selected="" data-component="SegmentedControl.Button"><button aria-pressed="true" class="prc-SegmentedControl-Button-E48xz" type="button" style="--separator-color:transparent"><span class="prc-SegmentedControl-Content-1COlk segmentedControl-content"><div class="prc-SegmentedControl-Text-7S2y2 segmentedControl-text" data-text="Preview">Preview</div></span></button></li><li class="prc-SegmentedControl-Item-tSCQh" data-component="SegmentedControl.Button"><button aria-pressed="false" class="prc-SegmentedControl-Button-E48xz" type="button" style="--separator-color:var(--borderColor-default)"><span class="prc-SegmentedControl-Content-1COlk segmentedControl-content"><div class="prc-SegmentedControl-Text-7S2y2 segmentedControl-text" data-text="Code">Code</div></span></button></li><li class="prc-SegmentedControl-Item-tSCQh" data-component="SegmentedControl.Button"><button aria-pressed="false" class="prc-SegmentedControl-Button-E48xz" type="button" style="--separator-color:var(--borderColor-default)"><span class="prc-SegmentedControl-Content-1COlk segmentedControl-content"><div class="prc-SegmentedControl-Text-7S2y2 segmentedControl-text" data-text="Blame">Blame</div></span></button></li></ul><div class="d-none"></div><div class="react-code-size-details-in-header CodeSizeDetails-module__Box__VcD6l"><div class="text-mono CodeSizeDetails-module__Box_1__GVxQL"><div data-testid="blob-size" class="CodeSizeDetails-module__Truncate_1__lE93V prc-Truncate-Truncate-2G1eo" data-inline="true" title="10.6 KB" style="--truncate-max-width:100%"><span>268 lines (230 loc) · 10.6 KB</span></div></div></div></div><div class="BlobViewHeader-module__Box_3__ng6v2"><div class="d-none"></div><div class="react-blob-header-edit-and-raw-actions BlobViewHeader-module__Box_4__J4Y4W"><div class="d-none"></div><div class="prc-ButtonGroup-ButtonGroup-vFUrY" data-component="ButtonGroup"><div class="prc-ButtonGroup-Item-PqvDl"><a data-component="LinkButton" href="https://github.com/gleb7499/scada-mobile/raw/refs/heads/main/AUTH_FLOW.md" data-testid="raw-button" class="prc-Button-ButtonBase-9n-Xk LinkButton-module__linkButton__nFnov BlobViewHeader-module__LinkButton__X9kx2" data-loading="false" data-no-visuals="true" data-size="small" data-variant="default"><span data-component="buttonContent" data-align="center" class="prc-Button-ButtonContent-Iohp5"><span data-component="text" class="prc-Button-Label-FWkx3">Raw</span></span></a></div><div class="prc-ButtonGroup-Item-PqvDl"><button data-component="IconButton" type="button" data-testid="copy-raw-button" class="prc-Button-ButtonBase-9n-Xk prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="default" aria-labelledby="_R_qaucpala9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-copy" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="n" data-component="Tooltip" aria-hidden="true" id="_R_qaucpala9lik5_">Copy raw file</span></div><div class="prc-ButtonGroup-Item-PqvDl"><button data-component="IconButton" type="button" data-testid="download-raw-button" class="prc-Button-ButtonBase-9n-Xk BlobViewHeader-module__downloadButton__ef459 prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="default" aria-labelledby="_R_eaucpala9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-download" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M2.75 14A1.75 1.75 0 0 1 1 12.25v-2.5a.75.75 0 0 1 1.5 0v2.5c0 .138.112.25.25.25h10.5a.25.25 0 0 0 .25-.25v-2.5a.75.75 0 0 1 1.5 0v2.5A1.75 1.75 0 0 1 13.25 14Z"></path><path d="M7.25 7.689V2a.75.75 0 0 1 1.5 0v5.689l1.97-1.969a.749.749 0 1 1 1.06 1.06l-3.25 3.25a.749.749 0 0 1-1.06 0L4.22 6.78a.749.749 0 1 1 1.06-1.06l1.97 1.969Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="n" data-component="Tooltip" aria-hidden="true" id="_R_eaucpala9lik5_">Download raw file</span></div></div></div><button data-component="IconButton" type="button" aria-pressed="false" class="prc-Button-ButtonBase-9n-Xk tmp-mr-2 TableOfContents-module__IconButton__jrlNM prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="invisible" aria-labelledby="_R_3ucpala9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-list-unordered" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M5.75 2.5h8.5a.75.75 0 0 1 0 1.5h-8.5a.75.75 0 0 1 0-1.5Zm0 5h8.5a.75.75 0 0 1 0 1.5h-8.5a.75.75 0 0 1 0-1.5Zm0 5h8.5a.75.75 0 0 1 0 1.5h-8.5a.75.75 0 0 1 0-1.5ZM2 14a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm1-6a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM2 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="n" data-component="Tooltip" aria-hidden="true" id="_R_3ucpala9lik5_">Outline</span><div class="react-blob-header-edit-and-raw-actions-combined"><button data-component="IconButton" type="button" title="More file actions" data-testid="more-file-actions-button" aria-haspopup="true" aria-expanded="false" tabindex="0" class="prc-Button-ButtonBase-9n-Xk js-blob-dropdown-click BlobViewHeader-module__IconButton__XrMQY prc-Button-IconButton-fyge7" data-loading="false" data-no-visuals="true" data-size="small" data-variant="invisible" aria-labelledby="_R_fkecpala9lik5_" id="_R_kecpala9lik5_"><svg data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-kebab-horizontal" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align:text-bottom"><path d="M8 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3ZM1.5 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Zm13 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z"></path></svg></button><span class="prc-TooltipV2-Tooltip-tLeuB" data-direction="nw" data-component="Tooltip" aria-hidden="true" id="_R_fkecpala9lik5_">Edit and raw actions</span></div></div></div></div><div></div></div><div class="BlobViewContent-module__blobContentWrapper__JS0W6"><section aria-labelledby="file-name-id-wide file-name-id-mobile" class="BlobContent-module__blobContentSection__VOgZq BlobContent-module__blobContentSectionMarkdown__mPLOK" style="margin-top:46px"><div class="js-snippet-clipboard-copy-unpositioned BlobContent-module__markdownBlob__T8jpG" data-hpc="true" containertiming="hpc"><article class="markdown-body entry-content container-lg" itemprop="text"><div class="markdown-heading" dir="auto"><h1 tabindex="-1" class="heading-element" dir="auto">Поток аутентификации (SCADA Mobile)</h1><a id="user-content-поток-аутентификации-scada-mobile" class="anchor" aria-label="Permalink: Поток аутентификации (SCADA Mobile)" href="#поток-аутентификации-scada-mobile"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">Purpose</h2><a id="user-content-purpose" class="anchor" aria-label="Permalink: Purpose" href="#purpose"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<p dir="auto">Документ описывает полный поток аутентификации и авторизации: от входа пользователя до WebSocket-соединения.</p>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">Table of contents</h2><a id="user-content-table-of-contents" class="anchor" aria-label="Permalink: Table of contents" href="#table-of-contents"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li><a href="#purpose">Purpose</a></li>
<li><a href="#%D0%BE%D0%B1%D1%89%D0%B0%D1%8F-%D0%B4%D0%B8%D0%B0%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B0">Общая диаграмма</a></li>
<li><a href="#%D1%8D%D1%82%D0%B0%D0%BF-1-%D0%B2%D1%85%D0%BE%D0%B4-login">Этап 1: Вход (login)</a></li>
<li><a href="#%D1%8D%D1%82%D0%B0%D0%BF-2-%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-access-token">Этап 2: Использование access token</a></li>
<li><a href="#%D1%8D%D1%82%D0%B0%D0%BF-3-%D1%80%D0%BE%D1%82%D0%B0%D1%86%D0%B8%D1%8F-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%BE%D0%B2-refresh">Этап 3: Ротация токенов (refresh)</a></li>
<li><a href="#%D1%8D%D1%82%D0%B0%D0%BF-4-%D0%B2%D1%8B%D1%85%D0%BE%D0%B4-logout">Этап 4: Выход (logout)</a></li>
<li><a href="#%D1%8D%D1%82%D0%B0%D0%BF-5-websocket-%D0%B0%D1%83%D1%82%D0%B5%D0%BD%D1%82%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%86%D0%B8%D1%8F">Этап 5: WebSocket аутентификация</a></li>
<li><a href="#%D1%8D%D1%82%D0%B0%D0%BF-6-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF-%D0%BA-%D0%B0%D0%B4%D0%BC%D0%B8%D0%BD-%D0%BF%D0%B0%D0%BD%D0%B5%D0%BB%D0%B8">Этап 6: Доступ к админ-панели</a></li>
<li><a href="#%D1%81%D1%80%D0%B0%D0%B2%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5-%D1%82%D0%BE%D0%BA%D0%B5%D0%BD%D0%BE%D0%B2">Сравнение токенов</a></li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">Общая диаграмма</h2><a id="user-content-общая-диаграмма" class="anchor" aria-label="Permalink: Общая диаграмма" href="#общая-диаграмма"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<section class="js-render-needs-enrichment render-needs-enrichment position-relative" data-identity="d926c65a-56b5-4ab1-82a6-7244d7d1bce4" data-host="https://viewscreen.githubusercontent.com" data-src="https://viewscreen.githubusercontent.com/markdown/mermaid?docs_host=https%3A%2F%2Fdocs.github.com" data-type="mermaid" aria-label="mermaid rendered output container">
  <div class="js-render-enrichment-target" data-json="{&quot;data&quot;:&quot;flowchart TB\n    subgraph Client[\&quot;Клиент (Frontend)\&quot;]\n        Login[\&quot;Экран входа\&quot;]\n        Storage[\&quot;localStorage\&quot;]\n        ApiClient[\&quot;API Client\&quot;]\n        WsClient[\&quot;WebSocket Client\&quot;]\n    end\n\n    subgraph Backend[\&quot;Backend (Spring Security)\&quot;]\n        AuthController[\&quot;AuthController\&quot;]\n        AuthService[\&quot;AuthService\&quot;]\n        JwtProvider[\&quot;JwtTokenProvider\&quot;]\n        JwtDecoder[\&quot;JwtDecoder\&quot;]\n        UserRepo[\&quot;UserAuthRepository\&quot;]\n        TokenRepo[\&quot;RefreshTokenRepository\&quot;]\n        SecurityFilter[\&quot;Security Filter Chain\&quot;]\n        WsInterceptor[\&quot;WebSocketJwtInterceptor\&quot;]\n    end\n\n    subgraph DB[\&quot;БД (PostgreSQL)\&quot;]\n        Users[\&quot;users\&quot;]\n        RefreshTokens[\&quot;refresh_tokens\&quot;]\n    end\n\n    Login --&gt;|\&quot;POST /auth/login&lt;br/&gt;{workerCode, password}\&quot;| AuthController\n    AuthController --&gt; AuthService\n    AuthService --&gt; UserRepo\n    UserRepo --&gt; Users\n    AuthService --&gt; JwtProvider\n    JwtProvider --&gt;|\&quot;accessToken (JWT)\&quot;| AuthController\n    JwtProvider --&gt;|\&quot;refreshToken (UUID)\&quot;| AuthService\n    AuthService --&gt; TokenRepo\n    TokenRepo --&gt; RefreshTokens\n    AuthController --&gt;|\&quot;{accessToken, refreshToken}\&quot;| Storage\n\n    Storage --&gt;|\&quot;Authorization: Bearer &lt;accessToken&gt;\&quot;| ApiClient\n    ApiClient --&gt;|\&quot;Запрос к API\&quot;| SecurityFilter\n    SecurityFilter --&gt; JwtDecoder\n    JwtDecoder --&gt;|\&quot;Валидация подписи,&lt;br/&gt;issuer, audience, expiry\&quot;| SecurityFilter\n\n    Storage --&gt;|\&quot;?token=&lt;accessToken&gt;\&quot;| WsClient\n    WsClient --&gt;|\&quot;WebSocket handshake\&quot;| WsInterceptor\n    WsInterceptor --&gt; JwtProvider\n\n    ApiClient --&gt;|\&quot;POST /auth/refresh&lt;br/&gt;{refreshToken}\&quot;| AuthController\n    AuthController --&gt; AuthService\n    AuthService --&gt; TokenRepo\n    TokenRepo --&gt; RefreshTokens\n    AuthService --&gt;|\&quot;Новая пара токенов\&quot;| AuthController\n    AuthController --&gt;|\&quot;Обновить в Storage\&quot;| Storage\n&quot;}" data-plain="flowchart TB
    subgraph Client[&quot;Клиент (Frontend)&quot;]
        Login[&quot;Экран входа&quot;]
        Storage[&quot;localStorage&quot;]
        ApiClient[&quot;API Client&quot;]
        WsClient[&quot;WebSocket Client&quot;]
    end

    subgraph Backend[&quot;Backend (Spring Security)&quot;]
        AuthController[&quot;AuthController&quot;]
        AuthService[&quot;AuthService&quot;]
        JwtProvider[&quot;JwtTokenProvider&quot;]
        JwtDecoder[&quot;JwtDecoder&quot;]
        UserRepo[&quot;UserAuthRepository&quot;]
        TokenRepo[&quot;RefreshTokenRepository&quot;]
        SecurityFilter[&quot;Security Filter Chain&quot;]
        WsInterceptor[&quot;WebSocketJwtInterceptor&quot;]
    end

    subgraph DB[&quot;БД (PostgreSQL)&quot;]
        Users[&quot;users&quot;]
        RefreshTokens[&quot;refresh_tokens&quot;]
    end

    Login --&gt;|&quot;POST /auth/login&lt;br/&gt;{workerCode, password}&quot;| AuthController
    AuthController --&gt; AuthService
    AuthService --&gt; UserRepo
    UserRepo --&gt; Users
    AuthService --&gt; JwtProvider
    JwtProvider --&gt;|&quot;accessToken (JWT)&quot;| AuthController
    JwtProvider --&gt;|&quot;refreshToken (UUID)&quot;| AuthService
    AuthService --&gt; TokenRepo
    TokenRepo --&gt; RefreshTokens
    AuthController --&gt;|&quot;{accessToken, refreshToken}&quot;| Storage

    Storage --&gt;|&quot;Authorization: Bearer &lt;accessToken&gt;&quot;| ApiClient
    ApiClient --&gt;|&quot;Запрос к API&quot;| SecurityFilter
    SecurityFilter --&gt; JwtDecoder
    JwtDecoder --&gt;|&quot;Валидация подписи,&lt;br/&gt;issuer, audience, expiry&quot;| SecurityFilter

    Storage --&gt;|&quot;?token=&lt;accessToken&gt;&quot;| WsClient
    WsClient --&gt;|&quot;WebSocket handshake&quot;| WsInterceptor
    WsInterceptor --&gt; JwtProvider

    ApiClient --&gt;|&quot;POST /auth/refresh&lt;br/&gt;{refreshToken}&quot;| AuthController
    AuthController --&gt; AuthService
    AuthService --&gt; TokenRepo
    TokenRepo --&gt; RefreshTokens
    AuthService --&gt;|&quot;Новая пара токенов&quot;| AuthController
    AuthController --&gt;|&quot;Обновить в Storage&quot;| Storage
" dir="auto">
    <div class="render-plaintext-hidden" dir="auto">
      <pre lang="mermaid" aria-label="Raw mermaid code">flowchart TB
    subgraph Client["Клиент (Frontend)"]
        Login["Экран входа"]
        Storage["localStorage"]
        ApiClient["API Client"]
        WsClient["WebSocket Client"]
    end

    subgraph Backend["Backend (Spring Security)"]
        AuthController["AuthController"]
        AuthService["AuthService"]
        JwtProvider["JwtTokenProvider"]
        JwtDecoder["JwtDecoder"]
        UserRepo["UserAuthRepository"]
        TokenRepo["RefreshTokenRepository"]
        SecurityFilter["Security Filter Chain"]
        WsInterceptor["WebSocketJwtInterceptor"]
    end

    subgraph DB["БД (PostgreSQL)"]
        Users["users"]
        RefreshTokens["refresh_tokens"]
    end

    Login --&gt;|"POST /auth/login&lt;br/&gt;{workerCode, password}"| AuthController
    AuthController --&gt; AuthService
    AuthService --&gt; UserRepo
    UserRepo --&gt; Users
    AuthService --&gt; JwtProvider
    JwtProvider --&gt;|"accessToken (JWT)"| AuthController
    JwtProvider --&gt;|"refreshToken (UUID)"| AuthService
    AuthService --&gt; TokenRepo
    TokenRepo --&gt; RefreshTokens
    AuthController --&gt;|"{accessToken, refreshToken}"| Storage

    Storage --&gt;|"Authorization: Bearer &lt;accessToken&gt;"| ApiClient
    ApiClient --&gt;|"Запрос к API"| SecurityFilter
    SecurityFilter --&gt; JwtDecoder
    JwtDecoder --&gt;|"Валидация подписи,&lt;br/&gt;issuer, audience, expiry"| SecurityFilter

    Storage --&gt;|"?token=&lt;accessToken&gt;"| WsClient
    WsClient --&gt;|"WebSocket handshake"| WsInterceptor
    WsInterceptor --&gt; JwtProvider

    ApiClient --&gt;|"POST /auth/refresh&lt;br/&gt;{refreshToken}"| AuthController
    AuthController --&gt; AuthService
    AuthService --&gt; TokenRepo
    TokenRepo --&gt; RefreshTokens
    AuthService --&gt;|"Новая пара токенов"| AuthController
    AuthController --&gt;|"Обновить в Storage"| Storage
</pre>
    </div>
  </div>
  <span class="js-render-enrichment-loader d-flex flex-justify-center flex-items-center width-full" style="min-height:100px" role="presentation">
    <span data-view-component="true">
  <svg style="box-sizing: content-box; color: var(--color-icon-primary);" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" data-view-component="true" class="octospinner mx-auto tmp-mx-auto anim-rotate">
    <circle cx="8" cy="8" r="7" stroke="currentColor" stroke-opacity="0.25" stroke-width="2" vector-effect="non-scaling-stroke" fill="none"></circle>
    <path d="M15 8a7.002 7.002 0 00-7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" vector-effect="non-scaling-stroke"></path>
</svg>    <span class="sr-only">Loading</span>
</span>
  </span>
</section>

<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">Этап 1: Вход (login)</h2><a id="user-content-этап-1-вход-login" class="anchor" aria-label="Permalink: Этап 1: Вход (login)" href="#этап-1-вход-login"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<section class="js-render-needs-enrichment render-needs-enrichment position-relative" data-identity="cf26dd82-5506-41cf-9cac-aec21add3591" data-host="https://viewscreen.githubusercontent.com" data-src="https://viewscreen.githubusercontent.com/markdown/mermaid?docs_host=https%3A%2F%2Fdocs.github.com" data-type="mermaid" aria-label="mermaid rendered output container">
  <div class="js-render-enrichment-target" data-json="{&quot;data&quot;:&quot;sequenceDiagram\n    participant Client as Frontend\n    participant Auth as AuthController\n    participant Service as AuthService\n    participant UserRepo as UserAuthJpaAdapter\n    participant TokenRepo as RefreshTokenJpaAdapter\n    participant DB as БД\n\n    Client-&gt;&gt;Auth: POST /auth/login&lt;br/&gt;{workerCode, password}\n    Auth-&gt;&gt;Service: authenticate(workerCode, password)\n    Service-&gt;&gt;UserRepo: findByCode(workerCode)\n    UserRepo-&gt;&gt;DB: SELECT * FROM users WHERE code = ?\n    DB--&gt;&gt;UserRepo: UserEntity\n    UserRepo--&gt;&gt;Service: Optional&lt;UserEntity&gt;\n\n    alt Пользователь не найден\n        Service--&gt;&gt;Auth: AuthenticationException\n        Auth--&gt;&gt;Client: 401 Unauthorized\n    else Пользователь найден\n        Service-&gt;&gt;Service: BCrypt.checkpw(password, hash)\n        alt Пароль неверный\n            Service--&gt;&gt;Auth: AuthenticationException\n            Auth--&gt;&gt;Client: 401 Unauthorized\n        else Пароль верный\n            Service-&gt;&gt;Service: generateAccessToken(userId, role)\n            Note over Service: JWT HS256, 15 мин&lt;br/&gt;sub=userId, role=ROLE_*\n            Service-&gt;&gt;Service: generateRefreshToken()\n            Note over Service: UUID, 7 дней\n            Service-&gt;&gt;Service: SHA-256(refreshToken)\n            Service-&gt;&gt;TokenRepo: save(tokenHash, userId, expiresAt)\n            TokenRepo-&gt;&gt;DB: INSERT INTO refresh_tokens\n            TokenRepo--&gt;&gt;Service: RefreshTokenEntity\n            Service--&gt;&gt;Auth: AuthLoginResponseDTO\n            Auth--&gt;&gt;Client: 200 OK&lt;br/&gt;{accessToken, refreshToken, userId, code, fullName, role}\n        end\n    end\n&quot;}" data-plain="sequenceDiagram
    participant Client as Frontend
    participant Auth as AuthController
    participant Service as AuthService
    participant UserRepo as UserAuthJpaAdapter
    participant TokenRepo as RefreshTokenJpaAdapter
    participant DB as БД

    Client-&gt;&gt;Auth: POST /auth/login&lt;br/&gt;{workerCode, password}
    Auth-&gt;&gt;Service: authenticate(workerCode, password)
    Service-&gt;&gt;UserRepo: findByCode(workerCode)
    UserRepo-&gt;&gt;DB: SELECT * FROM users WHERE code = ?
    DB--&gt;&gt;UserRepo: UserEntity
    UserRepo--&gt;&gt;Service: Optional&lt;UserEntity&gt;

    alt Пользователь не найден
        Service--&gt;&gt;Auth: AuthenticationException
        Auth--&gt;&gt;Client: 401 Unauthorized
    else Пользователь найден
        Service-&gt;&gt;Service: BCrypt.checkpw(password, hash)
        alt Пароль неверный
            Service--&gt;&gt;Auth: AuthenticationException
            Auth--&gt;&gt;Client: 401 Unauthorized
        else Пароль верный
            Service-&gt;&gt;Service: generateAccessToken(userId, role)
            Note over Service: JWT HS256, 15 мин&lt;br/&gt;sub=userId, role=ROLE_*
            Service-&gt;&gt;Service: generateRefreshToken()
            Note over Service: UUID, 7 дней
            Service-&gt;&gt;Service: SHA-256(refreshToken)
            Service-&gt;&gt;TokenRepo: save(tokenHash, userId, expiresAt)
            TokenRepo-&gt;&gt;DB: INSERT INTO refresh_tokens
            TokenRepo--&gt;&gt;Service: RefreshTokenEntity
            Service--&gt;&gt;Auth: AuthLoginResponseDTO
            Auth--&gt;&gt;Client: 200 OK&lt;br/&gt;{accessToken, refreshToken, userId, code, fullName, role}
        end
    end
" dir="auto">
    <div class="render-plaintext-hidden" dir="auto">
      <pre lang="mermaid" aria-label="Raw mermaid code">sequenceDiagram
    participant Client as Frontend
    participant Auth as AuthController
    participant Service as AuthService
    participant UserRepo as UserAuthJpaAdapter
    participant TokenRepo as RefreshTokenJpaAdapter
    participant DB as БД

    Client-&gt;&gt;Auth: POST /auth/login&lt;br/&gt;{workerCode, password}
    Auth-&gt;&gt;Service: authenticate(workerCode, password)
    Service-&gt;&gt;UserRepo: findByCode(workerCode)
    UserRepo-&gt;&gt;DB: SELECT * FROM users WHERE code = ?
    DB--&gt;&gt;UserRepo: UserEntity
    UserRepo--&gt;&gt;Service: Optional&lt;UserEntity&gt;

    alt Пользователь не найден
        Service--&gt;&gt;Auth: AuthenticationException
        Auth--&gt;&gt;Client: 401 Unauthorized
    else Пользователь найден
        Service-&gt;&gt;Service: BCrypt.checkpw(password, hash)
        alt Пароль неверный
            Service--&gt;&gt;Auth: AuthenticationException
            Auth--&gt;&gt;Client: 401 Unauthorized
        else Пароль верный
            Service-&gt;&gt;Service: generateAccessToken(userId, role)
            Note over Service: JWT HS256, 15 мин&lt;br/&gt;sub=userId, role=ROLE_*
            Service-&gt;&gt;Service: generateRefreshToken()
            Note over Service: UUID, 7 дней
            Service-&gt;&gt;Service: SHA-256(refreshToken)
            Service-&gt;&gt;TokenRepo: save(tokenHash, userId, expiresAt)
            TokenRepo-&gt;&gt;DB: INSERT INTO refresh_tokens
            TokenRepo--&gt;&gt;Service: RefreshTokenEntity
            Service--&gt;&gt;Auth: AuthLoginResponseDTO
            Auth--&gt;&gt;Client: 200 OK&lt;br/&gt;{accessToken, refreshToken, userId, code, fullName, role}
        end
    end
</pre>
    </div>
  </div>
  <span class="js-render-enrichment-loader d-flex flex-justify-center flex-items-center width-full" style="min-height:100px" role="presentation">
    <span data-view-component="true">
  <svg style="box-sizing: content-box; color: var(--color-icon-primary);" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" data-view-component="true" class="octospinner mx-auto tmp-mx-auto anim-rotate">
    <circle cx="8" cy="8" r="7" stroke="currentColor" stroke-opacity="0.25" stroke-width="2" vector-effect="non-scaling-stroke" fill="none"></circle>
    <path d="M15 8a7.002 7.002 0 00-7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" vector-effect="non-scaling-stroke"></path>
</svg>    <span class="sr-only">Loading</span>
</span>
  </span>
</section>

<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Поля ответа login</h3><a id="user-content-поля-ответа-login" class="anchor" aria-label="Permalink: Поля ответа login" href="#поля-ответа-login"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Поле</th>
<th>Описание</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>status</code></td>
<td><code>"success"</code></td>
</tr>
<tr>
<td><code>userId</code></td>
<td>Числовой ID пользователя</td>
</tr>
<tr>
<td><code>code</code></td>
<td>Worker code (логин)</td>
</tr>
<tr>
<td><code>fullName</code></td>
<td>ФИО сотрудника</td>
</tr>
<tr>
<td><code>role</code></td>
<td>Роль (<code>ADMIN</code>, <code>USER</code>)</td>
</tr>
<tr>
<td><code>accessToken</code></td>
<td>JWT для доступа к API</td>
</tr>
<tr>
<td><code>refreshToken</code></td>
<td>UUID для обновления пары</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">Этап 2: Использование access token</h2><a id="user-content-этап-2-использование-access-token" class="anchor" aria-label="Permalink: Этап 2: Использование access token" href="#этап-2-использование-access-token"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<section class="js-render-needs-enrichment render-needs-enrichment position-relative" data-identity="f7ea6a8b-66f7-4018-b508-389e8a1eb938" data-host="https://viewscreen.githubusercontent.com" data-src="https://viewscreen.githubusercontent.com/markdown/mermaid?docs_host=https%3A%2F%2Fdocs.github.com" data-type="mermaid" aria-label="mermaid rendered output container">
  <div class="js-render-enrichment-target" data-json="{&quot;data&quot;:&quot;sequenceDiagram\n    participant Client as Frontend\n    participant API as REST API\n    participant Filter as Security Filter\n    participant Decoder as JwtDecoder\n    participant Converter as JwtAuthenticationConverter\n\n    Client-&gt;&gt;API: GET /api/v1.0.0/workshops/topology&lt;br/&gt;Authorization: Bearer &lt;accessToken&gt;\n    API-&gt;&gt;Filter: Проверка токена\n    Filter-&gt;&gt;Decoder: decode(accessToken)\n    Decoder-&gt;&gt;Decoder: Проверка подписи (HS256)\n    Decoder-&gt;&gt;Decoder: Проверка issuer (scada-mobile)\n    Decoder-&gt;&gt;Decoder: Проверка audience (scada-mobile-api)\n    Decoder-&gt;&gt;Decoder: Проверка expiry\n    Decoder--&gt;&gt;Filter: Jwt\n    Filter-&gt;&gt;Converter: convert(Jwt)\n    Converter-&gt;&gt;Converter: Извлечь claim \&quot;role\&quot;\n    Converter--&gt;&gt;Filter: JwtAuthenticationToken (ROLE_*)\n    Filter--&gt;&gt;API: Продолжить обработку\n    API--&gt;&gt;Client: 200 OK + данные\n&quot;}" data-plain="sequenceDiagram
    participant Client as Frontend
    participant API as REST API
    participant Filter as Security Filter
    participant Decoder as JwtDecoder
    participant Converter as JwtAuthenticationConverter

    Client-&gt;&gt;API: GET /api/v1.0.0/workshops/topology&lt;br/&gt;Authorization: Bearer &lt;accessToken&gt;
    API-&gt;&gt;Filter: Проверка токена
    Filter-&gt;&gt;Decoder: decode(accessToken)
    Decoder-&gt;&gt;Decoder: Проверка подписи (HS256)
    Decoder-&gt;&gt;Decoder: Проверка issuer (scada-mobile)
    Decoder-&gt;&gt;Decoder: Проверка audience (scada-mobile-api)
    Decoder-&gt;&gt;Decoder: Проверка expiry
    Decoder--&gt;&gt;Filter: Jwt
    Filter-&gt;&gt;Converter: convert(Jwt)
    Converter-&gt;&gt;Converter: Извлечь claim &quot;role&quot;
    Converter--&gt;&gt;Filter: JwtAuthenticationToken (ROLE_*)
    Filter--&gt;&gt;API: Продолжить обработку
    API--&gt;&gt;Client: 200 OK + данные
" dir="auto">
    <div class="render-plaintext-hidden" dir="auto">
      <pre lang="mermaid" aria-label="Raw mermaid code">sequenceDiagram
    participant Client as Frontend
    participant API as REST API
    participant Filter as Security Filter
    participant Decoder as JwtDecoder
    participant Converter as JwtAuthenticationConverter

    Client-&gt;&gt;API: GET /api/v1.0.0/workshops/topology&lt;br/&gt;Authorization: Bearer &lt;accessToken&gt;
    API-&gt;&gt;Filter: Проверка токена
    Filter-&gt;&gt;Decoder: decode(accessToken)
    Decoder-&gt;&gt;Decoder: Проверка подписи (HS256)
    Decoder-&gt;&gt;Decoder: Проверка issuer (scada-mobile)
    Decoder-&gt;&gt;Decoder: Проверка audience (scada-mobile-api)
    Decoder-&gt;&gt;Decoder: Проверка expiry
    Decoder--&gt;&gt;Filter: Jwt
    Filter-&gt;&gt;Converter: convert(Jwt)
    Converter-&gt;&gt;Converter: Извлечь claim "role"
    Converter--&gt;&gt;Filter: JwtAuthenticationToken (ROLE_*)
    Filter--&gt;&gt;API: Продолжить обработку
    API--&gt;&gt;Client: 200 OK + данные
</pre>
    </div>
  </div>
  <span class="js-render-enrichment-loader d-flex flex-justify-center flex-items-center width-full" style="min-height:100px" role="presentation">
    <span data-view-component="true">
  <svg style="box-sizing: content-box; color: var(--color-icon-primary);" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" data-view-component="true" class="octospinner mx-auto tmp-mx-auto anim-rotate">
    <circle cx="8" cy="8" r="7" stroke="currentColor" stroke-opacity="0.25" stroke-width="2" vector-effect="non-scaling-stroke" fill="none"></circle>
    <path d="M15 8a7.002 7.002 0 00-7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" vector-effect="non-scaling-stroke"></path>
</svg>    <span class="sr-only">Loading</span>
</span>
  </span>
</section>

<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">Этап 3: Ротация токенов (refresh)</h2><a id="user-content-этап-3-ротация-токенов-refresh" class="anchor" aria-label="Permalink: Этап 3: Ротация токенов (refresh)" href="#этап-3-ротация-токенов-refresh"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<section class="js-render-needs-enrichment render-needs-enrichment position-relative" data-identity="39b9b295-9571-437b-8f6b-11ed4aad6edb" data-host="https://viewscreen.githubusercontent.com" data-src="https://viewscreen.githubusercontent.com/markdown/mermaid?docs_host=https%3A%2F%2Fdocs.github.com" data-type="mermaid" aria-label="mermaid rendered output container">
  <div class="js-render-enrichment-target" data-json="{&quot;data&quot;:&quot;sequenceDiagram\n    participant Client as Frontend\n    participant Auth as AuthController\n    participant Service as AuthService\n    participant TokenRepo as RefreshTokenRepository\n    participant DB as БД\n\n    Client-&gt;&gt;Auth: POST /auth/refresh&lt;br/&gt;{refreshToken}\n    Auth-&gt;&gt;Service: rotate(refreshToken)\n    Service-&gt;&gt;Service: SHA-256(refreshToken)\n    Service-&gt;&gt;TokenRepo: findByTokenHash(hash)\n    TokenRepo-&gt;&gt;DB: SELECT * FROM refresh_tokens WHERE token_hash = ?\n    DB--&gt;&gt;TokenRepo: RefreshTokenEntity\n    TokenRepo--&gt;&gt;Service: Optional&lt;RefreshTokenEntity&gt;\n\n    alt Токен не найден, истек или отозван\n        Service--&gt;&gt;Auth: AuthenticationException\n        Auth--&gt;&gt;Client: 401 Unauthorized\n    else Токен валиден\n        Service-&gt;&gt;TokenRepo: revoke(oldTokenId)\n        TokenRepo-&gt;&gt;DB: UPDATE refresh_tokens SET revoked = true\n        Service-&gt;&gt;Service: generateAccessToken(userId, role)\n        Service-&gt;&gt;Service: generateRefreshToken()\n        Service-&gt;&gt;Service: SHA-256(newRefreshToken)\n        Service-&gt;&gt;TokenRepo: save(newHash, userId, expiresAt)\n        TokenRepo-&gt;&gt;DB: INSERT INTO refresh_tokens\n        Service--&gt;&gt;Auth: AuthRefreshResponseDTO\n        Auth--&gt;&gt;Client: 200 OK&lt;br/&gt;{accessToken, refreshToken}\n    end\n&quot;}" data-plain="sequenceDiagram
    participant Client as Frontend
    participant Auth as AuthController
    participant Service as AuthService
    participant TokenRepo as RefreshTokenRepository
    participant DB as БД

    Client-&gt;&gt;Auth: POST /auth/refresh&lt;br/&gt;{refreshToken}
    Auth-&gt;&gt;Service: rotate(refreshToken)
    Service-&gt;&gt;Service: SHA-256(refreshToken)
    Service-&gt;&gt;TokenRepo: findByTokenHash(hash)
    TokenRepo-&gt;&gt;DB: SELECT * FROM refresh_tokens WHERE token_hash = ?
    DB--&gt;&gt;TokenRepo: RefreshTokenEntity
    TokenRepo--&gt;&gt;Service: Optional&lt;RefreshTokenEntity&gt;

    alt Токен не найден, истек или отозван
        Service--&gt;&gt;Auth: AuthenticationException
        Auth--&gt;&gt;Client: 401 Unauthorized
    else Токен валиден
        Service-&gt;&gt;TokenRepo: revoke(oldTokenId)
        TokenRepo-&gt;&gt;DB: UPDATE refresh_tokens SET revoked = true
        Service-&gt;&gt;Service: generateAccessToken(userId, role)
        Service-&gt;&gt;Service: generateRefreshToken()
        Service-&gt;&gt;Service: SHA-256(newRefreshToken)
        Service-&gt;&gt;TokenRepo: save(newHash, userId, expiresAt)
        TokenRepo-&gt;&gt;DB: INSERT INTO refresh_tokens
        Service--&gt;&gt;Auth: AuthRefreshResponseDTO
        Auth--&gt;&gt;Client: 200 OK&lt;br/&gt;{accessToken, refreshToken}
    end
" dir="auto">
    <div class="render-plaintext-hidden" dir="auto">
      <pre lang="mermaid" aria-label="Raw mermaid code">sequenceDiagram
    participant Client as Frontend
    participant Auth as AuthController
    participant Service as AuthService
    participant TokenRepo as RefreshTokenRepository
    participant DB as БД

    Client-&gt;&gt;Auth: POST /auth/refresh&lt;br/&gt;{refreshToken}
    Auth-&gt;&gt;Service: rotate(refreshToken)
    Service-&gt;&gt;Service: SHA-256(refreshToken)
    Service-&gt;&gt;TokenRepo: findByTokenHash(hash)
    TokenRepo-&gt;&gt;DB: SELECT * FROM refresh_tokens WHERE token_hash = ?
    DB--&gt;&gt;TokenRepo: RefreshTokenEntity
    TokenRepo--&gt;&gt;Service: Optional&lt;RefreshTokenEntity&gt;

    alt Токен не найден, истек или отозван
        Service--&gt;&gt;Auth: AuthenticationException
        Auth--&gt;&gt;Client: 401 Unauthorized
    else Токен валиден
        Service-&gt;&gt;TokenRepo: revoke(oldTokenId)
        TokenRepo-&gt;&gt;DB: UPDATE refresh_tokens SET revoked = true
        Service-&gt;&gt;Service: generateAccessToken(userId, role)
        Service-&gt;&gt;Service: generateRefreshToken()
        Service-&gt;&gt;Service: SHA-256(newRefreshToken)
        Service-&gt;&gt;TokenRepo: save(newHash, userId, expiresAt)
        TokenRepo-&gt;&gt;DB: INSERT INTO refresh_tokens
        Service--&gt;&gt;Auth: AuthRefreshResponseDTO
        Auth--&gt;&gt;Client: 200 OK&lt;br/&gt;{accessToken, refreshToken}
    end
</pre>
    </div>
  </div>
  <span class="js-render-enrichment-loader d-flex flex-justify-center flex-items-center width-full" style="min-height:100px" role="presentation">
    <span data-view-component="true">
  <svg style="box-sizing: content-box; color: var(--color-icon-primary);" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" data-view-component="true" class="octospinner mx-auto tmp-mx-auto anim-rotate">
    <circle cx="8" cy="8" r="7" stroke="currentColor" stroke-opacity="0.25" stroke-width="2" vector-effect="non-scaling-stroke" fill="none"></circle>
    <path d="M15 8a7.002 7.002 0 00-7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" vector-effect="non-scaling-stroke"></path>
</svg>    <span class="sr-only">Loading</span>
</span>
  </span>
</section>

<div class="markdown-heading" dir="auto"><h3 tabindex="-1" class="heading-element" dir="auto">Почему ротация важна</h3><a id="user-content-почему-ротация-важна" class="anchor" aria-label="Permalink: Почему ротация важна" href="#почему-ротация-важна"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<ul dir="auto">
<li>При компрометации access token злоумышленник имеет доступ максимум 15 минут.</li>
<li>При компрометации refresh token он будет отозван при следующем легитимном использовании.</li>
<li>Каждое обновление генерирует новую пару — старая пара становится недействительной.</li>
</ul>
<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">Этап 4: Выход (logout)</h2><a id="user-content-этап-4-выход-logout" class="anchor" aria-label="Permalink: Этап 4: Выход (logout)" href="#этап-4-выход-logout"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<section class="js-render-needs-enrichment render-needs-enrichment position-relative" data-identity="775d1074-043b-4bb5-822b-9aff72a5d3b4" data-host="https://viewscreen.githubusercontent.com" data-src="https://viewscreen.githubusercontent.com/markdown/mermaid?docs_host=https%3A%2F%2Fdocs.github.com" data-type="mermaid" aria-label="mermaid rendered output container">
  <div class="js-render-enrichment-target" data-json="{&quot;data&quot;:&quot;sequenceDiagram\n    participant Client as Frontend\n    participant Auth as AuthController\n    participant Service as AuthService\n    participant TokenRepo as RefreshTokenRepository\n    participant DB as БД\n\n    Client-&gt;&gt;Auth: POST /auth/logout&lt;br/&gt;{refreshToken}\n    Auth-&gt;&gt;Service: revoke(refreshToken)\n    Service-&gt;&gt;Service: SHA-256(refreshToken)\n    Service-&gt;&gt;TokenRepo: revokeByHash(hash)\n    TokenRepo-&gt;&gt;DB: UPDATE refresh_tokens SET revoked = true\n    TokenRepo--&gt;&gt;Service: OK\n    Service--&gt;&gt;Auth: OK\n    Auth--&gt;&gt;Client: 200 OK\n    Client-&gt;&gt;Client: Удалить токены из localStorage\n&quot;}" data-plain="sequenceDiagram
    participant Client as Frontend
    participant Auth as AuthController
    participant Service as AuthService
    participant TokenRepo as RefreshTokenRepository
    participant DB as БД

    Client-&gt;&gt;Auth: POST /auth/logout&lt;br/&gt;{refreshToken}
    Auth-&gt;&gt;Service: revoke(refreshToken)
    Service-&gt;&gt;Service: SHA-256(refreshToken)
    Service-&gt;&gt;TokenRepo: revokeByHash(hash)
    TokenRepo-&gt;&gt;DB: UPDATE refresh_tokens SET revoked = true
    TokenRepo--&gt;&gt;Service: OK
    Service--&gt;&gt;Auth: OK
    Auth--&gt;&gt;Client: 200 OK
    Client-&gt;&gt;Client: Удалить токены из localStorage
" dir="auto">
    <div class="render-plaintext-hidden" dir="auto">
      <pre lang="mermaid" aria-label="Raw mermaid code">sequenceDiagram
    participant Client as Frontend
    participant Auth as AuthController
    participant Service as AuthService
    participant TokenRepo as RefreshTokenRepository
    participant DB as БД

    Client-&gt;&gt;Auth: POST /auth/logout&lt;br/&gt;{refreshToken}
    Auth-&gt;&gt;Service: revoke(refreshToken)
    Service-&gt;&gt;Service: SHA-256(refreshToken)
    Service-&gt;&gt;TokenRepo: revokeByHash(hash)
    TokenRepo-&gt;&gt;DB: UPDATE refresh_tokens SET revoked = true
    TokenRepo--&gt;&gt;Service: OK
    Service--&gt;&gt;Auth: OK
    Auth--&gt;&gt;Client: 200 OK
    Client-&gt;&gt;Client: Удалить токены из localStorage
</pre>
    </div>
  </div>
  <span class="js-render-enrichment-loader d-flex flex-justify-center flex-items-center width-full" style="min-height:100px" role="presentation">
    <span data-view-component="true">
  <svg style="box-sizing: content-box; color: var(--color-icon-primary);" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" data-view-component="true" class="octospinner mx-auto tmp-mx-auto anim-rotate">
    <circle cx="8" cy="8" r="7" stroke="currentColor" stroke-opacity="0.25" stroke-width="2" vector-effect="non-scaling-stroke" fill="none"></circle>
    <path d="M15 8a7.002 7.002 0 00-7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" vector-effect="non-scaling-stroke"></path>
</svg>    <span class="sr-only">Loading</span>
</span>
  </span>
</section>

<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">Этап 5: WebSocket аутентификация</h2><a id="user-content-этап-5-websocket-аутентификация" class="anchor" aria-label="Permalink: Этап 5: WebSocket аутентификация" href="#этап-5-websocket-аутентификация"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<section class="js-render-needs-enrichment render-needs-enrichment position-relative" data-identity="3f344e3d-7be8-4b84-902b-d592342c8abc" data-host="https://viewscreen.githubusercontent.com" data-src="https://viewscreen.githubusercontent.com/markdown/mermaid?docs_host=https%3A%2F%2Fdocs.github.com" data-type="mermaid" aria-label="mermaid rendered output container">
  <div class="js-render-enrichment-target" data-json="{&quot;data&quot;:&quot;sequenceDiagram\n    participant Client as Frontend\n    participant Ws as WebSocket Endpoint\n    participant Interceptor as WebSocketJwtInterceptor\n    participant JwtProvider as JwtTokenProvider\n\n    Client-&gt;&gt;Ws: GET /ws/live?token=&lt;accessToken&gt;\n    Ws-&gt;&gt;Interceptor: beforeHandshake(request, response, handler, attributes)\n    Interceptor-&gt;&gt;Interceptor: Извлечь token из query param\n    Interceptor-&gt;&gt;JwtProvider: validateToken(token)\n    JwtProvider--&gt;&gt;Interceptor: userId (или исключение)\n\n    alt Токен невалиден\n        Interceptor--&gt;&gt;Ws: false (отклонить handshake)\n        Ws--&gt;&gt;Client: 401 Unauthorized\n    else Токен валиден\n        Interceptor-&gt;&gt;Interceptor: attributes.put(\&quot;userId\&quot;, userId)\n        Interceptor--&gt;&gt;Ws: true (продолжить handshake)\n        Ws--&gt;&gt;Client: 101 Switching Protocols\n    end\n&quot;}" data-plain="sequenceDiagram
    participant Client as Frontend
    participant Ws as WebSocket Endpoint
    participant Interceptor as WebSocketJwtInterceptor
    participant JwtProvider as JwtTokenProvider

    Client-&gt;&gt;Ws: GET /ws/live?token=&lt;accessToken&gt;
    Ws-&gt;&gt;Interceptor: beforeHandshake(request, response, handler, attributes)
    Interceptor-&gt;&gt;Interceptor: Извлечь token из query param
    Interceptor-&gt;&gt;JwtProvider: validateToken(token)
    JwtProvider--&gt;&gt;Interceptor: userId (или исключение)

    alt Токен невалиден
        Interceptor--&gt;&gt;Ws: false (отклонить handshake)
        Ws--&gt;&gt;Client: 401 Unauthorized
    else Токен валиден
        Interceptor-&gt;&gt;Interceptor: attributes.put(&quot;userId&quot;, userId)
        Interceptor--&gt;&gt;Ws: true (продолжить handshake)
        Ws--&gt;&gt;Client: 101 Switching Protocols
    end
" dir="auto">
    <div class="render-plaintext-hidden" dir="auto">
      <pre lang="mermaid" aria-label="Raw mermaid code">sequenceDiagram
    participant Client as Frontend
    participant Ws as WebSocket Endpoint
    participant Interceptor as WebSocketJwtInterceptor
    participant JwtProvider as JwtTokenProvider

    Client-&gt;&gt;Ws: GET /ws/live?token=&lt;accessToken&gt;
    Ws-&gt;&gt;Interceptor: beforeHandshake(request, response, handler, attributes)
    Interceptor-&gt;&gt;Interceptor: Извлечь token из query param
    Interceptor-&gt;&gt;JwtProvider: validateToken(token)
    JwtProvider--&gt;&gt;Interceptor: userId (или исключение)

    alt Токен невалиден
        Interceptor--&gt;&gt;Ws: false (отклонить handshake)
        Ws--&gt;&gt;Client: 401 Unauthorized
    else Токен валиден
        Interceptor-&gt;&gt;Interceptor: attributes.put("userId", userId)
        Interceptor--&gt;&gt;Ws: true (продолжить handshake)
        Ws--&gt;&gt;Client: 101 Switching Protocols
    end
</pre>
    </div>
  </div>
  <span class="js-render-enrichment-loader d-flex flex-justify-center flex-items-center width-full" style="min-height:100px" role="presentation">
    <span data-view-component="true">
  <svg style="box-sizing: content-box; color: var(--color-icon-primary);" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" data-view-component="true" class="octospinner mx-auto tmp-mx-auto anim-rotate">
    <circle cx="8" cy="8" r="7" stroke="currentColor" stroke-opacity="0.25" stroke-width="2" vector-effect="non-scaling-stroke" fill="none"></circle>
    <path d="M15 8a7.002 7.002 0 00-7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" vector-effect="non-scaling-stroke"></path>
</svg>    <span class="sr-only">Loading</span>
</span>
  </span>
</section>

<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">Этап 6: Доступ к админ-панели</h2><a id="user-content-этап-6-доступ-к-админ-панели" class="anchor" aria-label="Permalink: Этап 6: Доступ к админ-панели" href="#этап-6-доступ-к-админ-панели"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<section class="js-render-needs-enrichment render-needs-enrichment position-relative" data-identity="dce95233-3df3-446c-936a-0cb1f8cbd872" data-host="https://viewscreen.githubusercontent.com" data-src="https://viewscreen.githubusercontent.com/markdown/mermaid?docs_host=https%3A%2F%2Fdocs.github.com" data-type="mermaid" aria-label="mermaid rendered output container">
  <div class="js-render-enrichment-target" data-json="{&quot;data&quot;:&quot;sequenceDiagram\n    participant Client as Frontend\n    participant Admin as AdminUserController\n    participant Security as Spring Security\n\n    Client-&gt;&gt;Admin: GET /api/v1.0.0/admin/users/list&lt;br/&gt;Authorization: Bearer &lt;token&gt;\n    Admin-&gt;&gt;Security: @PreAuthorize(\&quot;hasRole('ADMIN')\&quot;)\n    Security-&gt;&gt;Security: Извлечь роль из JWT\n\n    alt Роль != ADMIN\n        Security--&gt;&gt;Admin: AccessDeniedException\n        Admin--&gt;&gt;Client: 403 Forbidden\n    else Роль == ADMIN\n        Security--&gt;&gt;Admin: Продолжить\n        Admin--&gt;&gt;Client: 200 OK + список пользователей\n    end\n&quot;}" data-plain="sequenceDiagram
    participant Client as Frontend
    participant Admin as AdminUserController
    participant Security as Spring Security

    Client-&gt;&gt;Admin: GET /api/v1.0.0/admin/users/list&lt;br/&gt;Authorization: Bearer &lt;token&gt;
    Admin-&gt;&gt;Security: @PreAuthorize(&quot;hasRole('ADMIN')&quot;)
    Security-&gt;&gt;Security: Извлечь роль из JWT

    alt Роль != ADMIN
        Security--&gt;&gt;Admin: AccessDeniedException
        Admin--&gt;&gt;Client: 403 Forbidden
    else Роль == ADMIN
        Security--&gt;&gt;Admin: Продолжить
        Admin--&gt;&gt;Client: 200 OK + список пользователей
    end
" dir="auto">
    <div class="render-plaintext-hidden" dir="auto">
      <pre lang="mermaid" aria-label="Raw mermaid code">sequenceDiagram
    participant Client as Frontend
    participant Admin as AdminUserController
    participant Security as Spring Security

    Client-&gt;&gt;Admin: GET /api/v1.0.0/admin/users/list&lt;br/&gt;Authorization: Bearer &lt;token&gt;
    Admin-&gt;&gt;Security: @PreAuthorize("hasRole('ADMIN')")
    Security-&gt;&gt;Security: Извлечь роль из JWT

    alt Роль != ADMIN
        Security--&gt;&gt;Admin: AccessDeniedException
        Admin--&gt;&gt;Client: 403 Forbidden
    else Роль == ADMIN
        Security--&gt;&gt;Admin: Продолжить
        Admin--&gt;&gt;Client: 200 OK + список пользователей
    end
</pre>
    </div>
  </div>
  <span class="js-render-enrichment-loader d-flex flex-justify-center flex-items-center width-full" style="min-height:100px" role="presentation">
    <span data-view-component="true">
  <svg style="box-sizing: content-box; color: var(--color-icon-primary);" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" data-view-component="true" class="octospinner mx-auto tmp-mx-auto anim-rotate">
    <circle cx="8" cy="8" r="7" stroke="currentColor" stroke-opacity="0.25" stroke-width="2" vector-effect="non-scaling-stroke" fill="none"></circle>
    <path d="M15 8a7.002 7.002 0 00-7-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" vector-effect="non-scaling-stroke"></path>
</svg>    <span class="sr-only">Loading</span>
</span>
  </span>
</section>

<div class="markdown-heading" dir="auto"><h2 tabindex="-1" class="heading-element" dir="auto">Сравнение токенов</h2><a id="user-content-сравнение-токенов" class="anchor" aria-label="Permalink: Сравнение токенов" href="#сравнение-токенов"><svg data-component="Octicon" class="octicon octicon-link" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z"></path></svg></a></div>
<markdown-accessiblity-table><table>
<thead>
<tr>
<th>Характеристика</th>
<th>Access Token</th>
<th>Refresh Token</th>
</tr>
</thead>
<tbody>
<tr>
<td>Формат</td>
<td>JWT (HS256)</td>
<td>UUID (plain text)</td>
</tr>
<tr>
<td>Срок действия</td>
<td>15 минут</td>
<td>7 дней</td>
</tr>
<tr>
<td>Хранение на клиенте</td>
<td>localStorage</td>
<td>localStorage</td>
</tr>
<tr>
<td>Хранение на сервере</td>
<td>Не хранится</td>
<td>SHA-256 хеш в БД</td>
</tr>
<tr>
<td>Назначение</td>
<td>Доступ к API</td>
<td>Получение новой пары</td>
</tr>
<tr>
<td>Передача</td>
<td>Header <code>Authorization: Bearer</code></td>
<td>Body JSON</td>
</tr>
<tr>
<td>Отзыв</td>
<td>Невозможен (stateless)</td>
<td>Возможен (флаг <code>revoked</code>)</td>
</tr>
<tr>
<td>Ротация</td>
<td>При каждом refresh</td>
<td>При каждом refresh</td>
</tr>
</tbody>
</table></markdown-accessiblity-table>
</article></div><div class="d-none"></div></section></div></div></div> </div> <!-- --> </div></div></div></div></div></div><div class="ScrollMarksContainer-module__scrollMarksContainer__Eu7uU" id="find-result-marks-container"></div><div class="d-none"></div><div class="d-none"></div></div> <!-- --> <!-- --> </div>
</react-app>




  </div>

</turbo-frame>

    </main>
  </div>

  </div>

          <footer class="footer f6 color-fg-muted color-border-subtle tmp-pt-7 tmp-pb-6 p-responsive" role="contentinfo"  >
  <h2 class='sr-only'>Footer</h2>

  


  <div class="d-flex flex-justify-center flex-items-center flex-column-reverse flex-lg-row flex-wrap flex-lg-nowrap">
    <div class="d-flex flex-items-center flex-shrink-0 mx-2">
      <a aria-label="GitHub Homepage" class="footer-octicon mr-2" href="https://github.com">
        <svg aria-hidden="true" data-component="Octicon" height="24" viewBox="0 0 24 24" version="1.1" width="24" data-view-component="true" class="octicon octicon-mark-github">
    <path d="M10.226 17.284c-2.965-.36-5.054-2.493-5.054-5.256 0-1.123.404-2.336 1.078-3.144-.292-.741-.247-2.314.09-2.965.898-.112 2.111.36 2.83 1.01.853-.269 1.752-.404 2.853-.404 1.1 0 1.999.135 2.807.382.696-.629 1.932-1.1 2.83-.988.315.606.36 2.179.067 2.942.72.854 1.101 2 1.101 3.167 0 2.763-2.089 4.852-5.098 5.234.763.494 1.28 1.572 1.28 2.807v2.336c0 .674.561 1.056 1.235.786 4.066-1.55 7.255-5.615 7.255-10.646C23.5 6.188 18.334 1 11.978 1 5.62 1 .5 6.188.5 12.545c0 4.986 3.167 9.12 7.435 10.669.606.225 1.19-.18 1.19-.786V20.63a2.9 2.9 0 0 1-1.078.224c-1.483 0-2.359-.808-2.987-2.313-.247-.607-.517-.966-1.034-1.033-.27-.023-.359-.135-.359-.27 0-.27.45-.471.898-.471.652 0 1.213.404 1.797 1.235.45.651.921.943 1.483.943.561 0 .92-.202 1.437-.719.382-.381.674-.718.944-.943"></path>
</svg>
</a>
      <span>
        &copy; 2026 GitHub,&nbsp;Inc.
      </span>
    </div>

    <nav aria-label="Footer">
      <h3 class="sr-only" id="sr-footer-heading">Footer navigation</h3>

      <ul class="list-style-none d-flex flex-justify-center flex-wrap mb-2 mb-lg-0" aria-labelledby="sr-footer-heading">


          <li class="mx-2">
            <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to Terms&quot;,&quot;label&quot;:&quot;text:terms&quot;}" href="https://docs.github.com/site-policy/github-terms/github-terms-of-service" data-view-component="true" class="Link--secondary Link">Terms</a>
          </li>

          <li class="mx-2">
            <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to privacy&quot;,&quot;label&quot;:&quot;text:privacy&quot;}" href="https://docs.github.com/site-policy/privacy-policies/github-privacy-statement" data-view-component="true" class="Link--secondary Link">Privacy</a>
          </li>


            <li class="mx-2">
              <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to security&quot;,&quot;label&quot;:&quot;text:security&quot;}" href="https://github.com/security" data-view-component="true" class="Link--secondary Link">Security</a>
            </li>

            <li class="mx-2">
              <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to status&quot;,&quot;label&quot;:&quot;text:status&quot;}" href="https://www.githubstatus.com/" data-view-component="true" class="Link--secondary Link">Status</a>
            </li>

          <li class="mx-2">
            <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to community&quot;,&quot;label&quot;:&quot;text:community&quot;}" href="https://github.community/" data-view-component="true" class="Link--secondary Link">Community</a>
          </li>

          <li class="mx-2">
            <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to docs&quot;,&quot;label&quot;:&quot;text:docs&quot;}" href="https://docs.github.com/" data-view-component="true" class="Link--secondary Link">Docs</a>
          </li>

          <li class="mx-2">
            <a data-analytics-event="{&quot;category&quot;:&quot;Footer&quot;,&quot;action&quot;:&quot;go to contact&quot;,&quot;label&quot;:&quot;text:contact&quot;}" href="https://support.github.com?tags=dotcom-footer" data-view-component="true" class="Link--secondary Link">Contact</a>
          </li>

          
<li class="mx-2" >
  <cookie-consent-link>
    <button
      type="button"
      class="Link--secondary underline-on-hover border-0 p-0 color-bg-transparent"
      data-action="click:cookie-consent-link#showConsentManagement"
      data-analytics-event="{&quot;location&quot;:&quot;footer&quot;,&quot;action&quot;:&quot;cookies&quot;,&quot;context&quot;:&quot;subfooter&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;label&quot;:&quot;cookies_link_subfooter_footer&quot;}"
    >
      Manage cookies
    </button>
  </cookie-consent-link>
</li>

  <li class="mx-2">
    <cookie-consent-link>
      <button
        type="button"
        class="Link--secondary underline-on-hover border-0 p-0 color-bg-transparent text-left"
        data-action="click:cookie-consent-link#showConsentManagement"
        data-analytics-event="{&quot;location&quot;:&quot;footer&quot;,&quot;action&quot;:&quot;dont_share_info&quot;,&quot;context&quot;:&quot;subfooter&quot;,&quot;tag&quot;:&quot;link&quot;,&quot;label&quot;:&quot;dont_share_info_link_subfooter_footer&quot;}"
      >
        Do not share my personal information
      </button>
    </cookie-consent-link>
  </li>

      </ul>
    </nav>
  </div>
</footer>



    <ghcc-consent id="ghcc" class="position-fixed bottom-0 left-0" style="z-index: 999999"
      data-locale="en"
      data-initial-cookie-consent-allowed=""
      data-cookie-consent-required="true"
    ></ghcc-consent>




  <div id="ajax-error-message" class="ajax-error-message flash flash-error" hidden>
    <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-alert">
    <path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"></path>
</svg>
    <button type="button" class="flash-close js-ajax-error-dismiss" aria-label="Dismiss error">
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-x">
    <path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path>
</svg>
    </button>
    You can’t perform that action at this time.
  </div>

    <template id="site-details-dialog">
  <details class="details-reset details-overlay details-overlay-dark lh-default color-fg-default hx_rsm" open>
    <summary role="button" aria-label="Close dialog"></summary>
    <details-dialog class="Box Box--overlay d-flex flex-column anim-fade-in fast hx_rsm-dialog hx_rsm-modal">
      <button class="Box-btn-octicon m-0 btn-octicon position-absolute right-0 top-0" type="button" aria-label="Close dialog" data-close-dialog>
        <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-x">
    <path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path>
</svg>
      </button>
      <div class="octocat-spinner tmp-my-6 js-details-dialog-spinner"></div>
    </details-dialog>
  </details>
</template>

    <div class="Popover js-hovercard-content position-absolute" style="display: none; outline: none;">
  <div class="Popover-message Popover-message--bottom-left Popover-message--large Box color-shadow-large" style="width:360px;">
  </div>
</div>

    <template id="snippet-clipboard-copy-button">
  <div class="zeroclipboard-container position-absolute right-0 top-0">
    <clipboard-copy aria-label="Copy code to clipboard" class="ClipboardButton btn js-clipboard-copy m-2 p-0" data-copy-feedback="Copied!" data-tooltip-direction="w">
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-copy js-clipboard-copy-icon m-2 tmp-m-2">
    <path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path>
</svg>
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-check js-clipboard-check-icon color-fg-success d-none m-2 tmp-m-2">
    <path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path>
</svg>
    </clipboard-copy>
  </div>
</template>
<template id="snippet-clipboard-copy-button-unpositioned">
  <div class="zeroclipboard-container">
    <clipboard-copy aria-label="Copy code to clipboard" class="ClipboardButton btn btn-invisible js-clipboard-copy m-2 p-0 d-flex flex-justify-center flex-items-center" data-copy-feedback="Copied!" data-tooltip-direction="w">
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-copy js-clipboard-copy-icon">
    <path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path>
</svg>
      <svg aria-hidden="true" data-component="Octicon" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-check js-clipboard-check-icon color-fg-success d-none">
    <path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path>
</svg>
    </clipboard-copy>
  </div>
</template>




    </div>
    <div id="js-global-screen-reader-notice" class="sr-only mt-n1" aria-live="polite" aria-atomic="true" ></div>
    <div id="js-global-screen-reader-notice-assertive" class="sr-only mt-n1" aria-live="assertive" aria-atomic="true"></div>
  </body>
</html>

