X\choro 10 hónapja
szülő
commit
ec699fe8e1
100 módosított fájl, 5473 hozzáadás és 61 törlés
  1. 247 0
      .editorconfig
  2. 9 3
      .vs/VSWorkspaceState.json
  3. BIN
      .vs/bitforum/CopilotIndices/17.12.38.29086/CodeChunks.db
  4. BIN
      .vs/bitforum/CopilotIndices/17.12.38.29086/SemanticSymbols.db
  5. BIN
      .vs/bitforum/CopilotIndices/17.12.38.29086/SemanticSymbols.db-shm
  6. BIN
      .vs/bitforum/CopilotIndices/17.12.38.29086/SemanticSymbols.db-wal
  7. BIN
      .vs/bitforum/FileContentIndex/28c3bbcd-9c46-4876-87f4-85ff945126da.vsidx
  8. BIN
      .vs/bitforum/FileContentIndex/866905bc-c62c-4bed-902d-fdff362b7296.vsidx
  9. BIN
      .vs/bitforum/FileContentIndex/9c65ecee-a25a-49d9-a918-f8af0d72c137.vsidx
  10. BIN
      .vs/bitforum/FileContentIndex/cb678a33-7edb-4ed2-a66c-11ba9f2fd319.vsidx
  11. BIN
      .vs/bitforum/FileContentIndex/d0783f80-7e60-4a8e-99d8-9be3f06af51e.vsidx
  12. BIN
      .vs/bitforum/FileContentIndex/e42ca33e-a3dc-4d28-a287-c3f7fa64719c.vsidx
  13. 1016 0
      .vs/bitforum/config/applicationhost.config
  14. BIN
      .vs/bitforum/copilot-chat/d1c88368/sessions/f858f589-f449-436d-8558-9227c939a730
  15. BIN
      .vs/bitforum/v17/.wsuo
  16. 14 27
      .vs/bitforum/v17/DocumentLayout.backup.json
  17. 12 25
      .vs/bitforum/v17/DocumentLayout.json
  18. BIN
      .vs/slnx.sqlite
  19. 22 0
      .vscode/launch.json
  20. 12 0
      backend/.gitignore
  21. BIN
      backend/.vs/ProjectEvaluation/bitforum.metadata.v9.bin
  22. BIN
      backend/.vs/ProjectEvaluation/bitforum.projects.v9.bin
  23. BIN
      backend/.vs/ProjectEvaluation/bitforum.strings.v9.bin
  24. BIN
      backend/.vs/bitforum/CopilotIndices/17.12.38.29086/CodeChunks.db
  25. BIN
      backend/.vs/bitforum/CopilotIndices/17.12.38.29086/SemanticSymbols.db
  26. BIN
      backend/.vs/bitforum/CopilotIndices/17.12.38.29086/SemanticSymbols.db-shm
  27. BIN
      backend/.vs/bitforum/CopilotIndices/17.12.38.29086/SemanticSymbols.db-wal
  28. BIN
      backend/.vs/bitforum/DesignTimeBuild/.dtbcache.v2
  29. BIN
      backend/.vs/bitforum/FileContentIndex/459dda02-cceb-47fb-a347-df62975c9ad4.vsidx
  30. BIN
      backend/.vs/bitforum/FileContentIndex/9957048d-4583-4710-a413-10deea870b74.vsidx
  31. BIN
      backend/.vs/bitforum/v17/.futdcache.v2
  32. BIN
      backend/.vs/bitforum/v17/.suo
  33. 55 3
      backend/.vs/bitforum/v17/DocumentLayout.backup.json
  34. 38 3
      backend/.vs/bitforum/v17/DocumentLayout.json
  35. 10 0
      backend/Areas/Identity/Pages/Account/AccessDenied.cshtml
  36. 23 0
      backend/Areas/Identity/Pages/Account/AccessDenied.cshtml.cs
  37. 8 0
      backend/Areas/Identity/Pages/Account/ConfirmEmail.cshtml
  38. 51 0
      backend/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs
  39. 8 0
      backend/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml
  40. 69 0
      backend/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml.cs
  41. 33 0
      backend/Areas/Identity/Pages/Account/ExternalLogin.cshtml
  42. 223 0
      backend/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs
  43. 41 0
      backend/Areas/Identity/Pages/Account/ForgotPassword.cshtml
  44. 84 0
      backend/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs
  45. 17 0
      backend/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml
  46. 25 0
      backend/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml.cs
  47. 10 0
      backend/Areas/Identity/Pages/Account/Lockout.cshtml
  48. 25 0
      backend/Areas/Identity/Pages/Account/Lockout.cshtml.cs
  49. 95 0
      backend/Areas/Identity/Pages/Account/Login.cshtml
  50. 141 0
      backend/Areas/Identity/Pages/Account/Login.cshtml.cs
  51. 39 0
      backend/Areas/Identity/Pages/Account/LoginWith2fa.cshtml
  52. 131 0
      backend/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs
  53. 29 0
      backend/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml
  54. 112 0
      backend/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml.cs
  55. 21 0
      backend/Areas/Identity/Pages/Account/Logout.cshtml
  56. 42 0
      backend/Areas/Identity/Pages/Account/Logout.cshtml.cs
  57. 35 0
      backend/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml
  58. 127 0
      backend/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs
  59. 33 0
      backend/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml
  60. 103 0
      backend/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs
  61. 25 0
      backend/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml
  62. 69 0
      backend/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs
  63. 12 0
      backend/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml
  64. 67 0
      backend/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml.cs
  65. 44 0
      backend/Areas/Identity/Pages/Account/Manage/Email.cshtml
  66. 171 0
      backend/Areas/Identity/Pages/Account/Manage/Email.cshtml.cs
  67. 53 0
      backend/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml
  68. 188 0
      backend/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs
  69. 53 0
      backend/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml
  70. 141 0
      backend/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml.cs
  71. 27 0
      backend/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml
  72. 82 0
      backend/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs
  73. 32 0
      backend/Areas/Identity/Pages/Account/Manage/Index.cshtml
  74. 118 0
      backend/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs
  75. 123 0
      backend/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs
  76. 29 0
      backend/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml
  77. 36 0
      backend/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs
  78. 24 0
      backend/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml
  79. 67 0
      backend/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml.cs
  80. 35 0
      backend/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml
  81. 114 0
      backend/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml.cs
  82. 25 0
      backend/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml
  83. 46 0
      backend/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml.cs
  84. 71 0
      backend/Areas/Identity/Pages/Account/Manage/TwoFactorAuthentication.cshtml
  85. 89 0
      backend/Areas/Identity/Pages/Account/Manage/TwoFactorAuthentication.cshtml.cs
  86. 29 0
      backend/Areas/Identity/Pages/Account/Manage/_Layout.cshtml
  87. 17 0
      backend/Areas/Identity/Pages/Account/Manage/_ManageNav.cshtml
  88. 10 0
      backend/Areas/Identity/Pages/Account/Manage/_StatusMessage.cshtml
  89. 1 0
      backend/Areas/Identity/Pages/Account/Manage/_ViewImports.cshtml
  90. 81 0
      backend/Areas/Identity/Pages/Account/Register.cshtml
  91. 180 0
      backend/Areas/Identity/Pages/Account/Register.cshtml.cs
  92. 29 0
      backend/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml
  93. 79 0
      backend/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs
  94. 41 0
      backend/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml
  95. 88 0
      backend/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml.cs
  96. 48 0
      backend/Areas/Identity/Pages/Account/ResetPassword.cshtml
  97. 117 0
      backend/Areas/Identity/Pages/Account/ResetPassword.cshtml.cs
  98. 17 0
      backend/Areas/Identity/Pages/Account/ResetPasswordConfirmation.cshtml
  99. 25 0
      backend/Areas/Identity/Pages/Account/ResetPasswordConfirmation.cshtml.cs
  100. 10 0
      backend/Areas/Identity/Pages/Account/_StatusMessage.cshtml

+ 247 - 0
.editorconfig

@@ -0,0 +1,247 @@
+# 상위 디렉터리에서 .editorconfig 설정을 상속하려면 아래 행을 제거하세요.
+root = true
+
+# C# 파일
+[*.cs]
+
+#### 코어 EditorConfig 옵션 ####
+
+# 들여쓰기 및 간격
+indent_size = 4
+indent_style = space
+tab_width = 4
+
+# 새 줄 기본 설정
+end_of_line = crlf
+insert_final_newline = false
+
+#### .NET 코드 작업 ####
+
+# 멤버 입력
+dotnet_hide_advanced_members = false
+dotnet_member_insertion_location = with_other_members_of_the_same_kind
+dotnet_property_generation_behavior = prefer_throwing_properties
+
+# 기호 검색
+dotnet_search_reference_assemblies = true
+
+#### .NET 코딩 규칙 ####
+
+# Using 구성
+dotnet_separate_import_directive_groups = false
+dotnet_sort_system_directives_first = false
+file_header_template = unset
+
+# this. 및 Me. 기본 설정
+dotnet_style_qualification_for_event = false
+dotnet_style_qualification_for_field = false
+dotnet_style_qualification_for_method = false
+dotnet_style_qualification_for_property = false
+
+# 언어 키워드 및 BCL 형식 기본 설정
+dotnet_style_predefined_type_for_locals_parameters_members = true
+dotnet_style_predefined_type_for_member_access = true
+
+# 괄호 기본 설정
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
+
+# 한정자 기본 설정
+dotnet_style_require_accessibility_modifiers = for_non_interface_members
+
+# 식 수준 기본 설정
+dotnet_prefer_system_hash_code = true
+dotnet_style_coalesce_expression = true
+dotnet_style_collection_initializer = true
+dotnet_style_explicit_tuple_names = true
+dotnet_style_namespace_match_folder = true
+dotnet_style_null_propagation = true
+dotnet_style_object_initializer = true
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_prefer_auto_properties = true
+dotnet_style_prefer_collection_expression = when_types_loosely_match
+dotnet_style_prefer_compound_assignment = true
+dotnet_style_prefer_conditional_expression_over_assignment = true
+dotnet_style_prefer_conditional_expression_over_return = true
+dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
+dotnet_style_prefer_inferred_anonymous_type_member_names = true
+dotnet_style_prefer_inferred_tuple_names = true
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true
+dotnet_style_prefer_simplified_boolean_expressions = true
+dotnet_style_prefer_simplified_interpolation = true
+
+# 필드 기본 설정
+dotnet_style_readonly_field = true
+
+# 매개 변수 기본 설정
+dotnet_code_quality_unused_parameters = all
+
+# 비표시 오류(Suppression) 기본 설정
+dotnet_remove_unnecessary_suppression_exclusions = none
+
+# 새 줄 기본 설정
+dotnet_style_allow_multiple_blank_lines_experimental = true
+dotnet_style_allow_statement_immediately_after_block_experimental = true
+
+#### C# 코딩 규칙 ####
+
+# var 기본 설정
+csharp_style_var_elsewhere = false
+csharp_style_var_for_built_in_types = false
+csharp_style_var_when_type_is_apparent = false
+
+# 식 본문 멤버
+csharp_style_expression_bodied_accessors = true
+csharp_style_expression_bodied_constructors = false
+csharp_style_expression_bodied_indexers = true
+csharp_style_expression_bodied_lambdas = true
+csharp_style_expression_bodied_local_functions = false
+csharp_style_expression_bodied_methods = false
+csharp_style_expression_bodied_operators = false
+csharp_style_expression_bodied_properties = true
+
+# 패턴 일치 기본 설정
+csharp_style_pattern_matching_over_as_with_null_check = true
+csharp_style_pattern_matching_over_is_with_cast_check = true
+csharp_style_prefer_extended_property_pattern = true
+csharp_style_prefer_not_pattern = true
+csharp_style_prefer_pattern_matching = true
+csharp_style_prefer_switch_expression = true
+
+# Null 검사 기본 설정
+csharp_style_conditional_delegate_call = true
+
+# 한정자 기본 설정
+csharp_prefer_static_anonymous_function = true
+csharp_prefer_static_local_function = true
+csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
+csharp_style_prefer_readonly_struct = true
+csharp_style_prefer_readonly_struct_member = true
+
+# 코드 블록 기본 설정
+csharp_prefer_braces = true
+csharp_prefer_simple_using_statement = true
+csharp_prefer_system_threading_lock = true
+csharp_style_namespace_declarations = block_scoped
+csharp_style_prefer_method_group_conversion = true
+csharp_style_prefer_primary_constructors = true
+csharp_style_prefer_top_level_statements = true
+
+# 식 수준 기본 설정
+csharp_prefer_simple_default_expression = true
+csharp_style_deconstructed_variable_declaration = true
+csharp_style_implicit_object_creation_when_type_is_apparent = true
+csharp_style_inlined_variable_declaration = true
+csharp_style_prefer_index_operator = true
+csharp_style_prefer_local_over_anonymous_function = true
+csharp_style_prefer_null_check_over_type_check = true
+csharp_style_prefer_range_operator = true
+csharp_style_prefer_tuple_swap = true
+csharp_style_prefer_utf8_string_literals = true
+csharp_style_throw_expression = true
+csharp_style_unused_value_assignment_preference = discard_variable
+csharp_style_unused_value_expression_statement_preference = discard_variable
+
+# 'using' 지시문 기본 설정
+csharp_using_directive_placement = outside_namespace
+
+# 새 줄 기본 설정
+csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
+csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true
+csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true
+csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
+csharp_style_allow_embedded_statements_on_same_line_experimental = true
+
+#### C# 서식 설정 규칙 ####
+
+# 새 줄 기본 설정
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_open_brace = all
+csharp_new_line_between_query_expression_clauses = true
+
+# 들여쓰기 기본 설정
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = true
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = true
+
+# 공간 기본 설정
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# 기본 설정 래핑
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#### 명명 스타일 ####
+
+# 명명 규칙
+
+dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
+dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
+dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
+
+dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.types_should_be_pascal_case.symbols = types
+dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
+
+dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
+
+# 기호 사양
+
+dotnet_naming_symbols.interface.applicable_kinds = interface
+dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interface.required_modifiers = 
+
+dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
+dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types.required_modifiers = 
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers = 
+
+# 명명 스타일
+
+dotnet_naming_style.pascal_case.required_prefix = 
+dotnet_naming_style.pascal_case.required_suffix = 
+dotnet_naming_style.pascal_case.word_separator = 
+dotnet_naming_style.pascal_case.capitalization = pascal_case
+
+dotnet_naming_style.begins_with_i.required_prefix = I
+dotnet_naming_style.begins_with_i.required_suffix = 
+dotnet_naming_style.begins_with_i.word_separator = 
+dotnet_naming_style.begins_with_i.capitalization = pascal_case
+
+[*]
+charset = utf-8

+ 9 - 3
.vs/VSWorkspaceState.json

@@ -1,10 +1,16 @@
 {
   "ExpandedNodes": [
     "",
-    "\\frontend",
+    "\\backend",
+    "\\backend\\Controllers",
     "\\frontend\\app",
-    "\\frontend\\app\\(auth)"
+    "\\frontend\\app\\(auth)",
+    "\\frontend\\app\\(auth)\\register",
+    "\\frontend\\app\\(auth)\\resetPassword",
+    "\\frontend\\components",
+    "\\frontend\\components\\ui",
+    "\\frontend\\public"
   ],
-  "SelectedNode": "\\frontend\\app\\(auth)",
+  "SelectedNode": "\\backend\\Controllers\\HomeController.cs",
   "PreviewInSolutionExplorer": false
 }

BIN
.vs/bitforum/CopilotIndices/17.12.38.29086/CodeChunks.db


BIN
.vs/bitforum/CopilotIndices/17.12.38.29086/SemanticSymbols.db


BIN
.vs/bitforum/CopilotIndices/17.12.38.29086/SemanticSymbols.db-shm


BIN
.vs/bitforum/CopilotIndices/17.12.38.29086/SemanticSymbols.db-wal


BIN
.vs/bitforum/FileContentIndex/28c3bbcd-9c46-4876-87f4-85ff945126da.vsidx


BIN
.vs/bitforum/FileContentIndex/866905bc-c62c-4bed-902d-fdff362b7296.vsidx


BIN
.vs/bitforum/FileContentIndex/9c65ecee-a25a-49d9-a918-f8af0d72c137.vsidx


BIN
.vs/bitforum/FileContentIndex/cb678a33-7edb-4ed2-a66c-11ba9f2fd319.vsidx


BIN
.vs/bitforum/FileContentIndex/d0783f80-7e60-4a8e-99d8-9be3f06af51e.vsidx


BIN
.vs/bitforum/FileContentIndex/e42ca33e-a3dc-4d28-a287-c3f7fa64719c.vsidx


+ 1016 - 0
.vs/bitforum/config/applicationhost.config

@@ -0,0 +1,1016 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    IIS configuration sections.
+
+    For schema documentation, see
+    %IIS_BIN%\config\schema\IIS_schema.xml.
+    
+    Please make a backup of this file before making any changes to it.
+
+    NOTE: The following environment variables are available to be used
+          within this file and are understood by the IIS Express.
+
+          %IIS_USER_HOME% - The IIS Express home directory for the user
+          %IIS_SITES_HOME% - The default home directory for sites
+          %IIS_BIN% - The location of the IIS Express binaries
+          %SYSTEMDRIVE% - The drive letter of %IIS_BIN%
+
+-->
+<configuration>
+
+    <!--
+
+        The <configSections> section controls the registration of sections.
+        Section is the basic unit of deployment, locking, searching and
+        containment for configuration settings.
+        
+        Every section belongs to one section group.
+        A section group is a container of logically-related sections.
+        
+        Sections cannot be nested.
+        Section groups may be nested.
+        
+        <section
+            name=""  [Required, Collection Key] [XML name of the section]
+            allowDefinition="Everywhere" [MachineOnly|MachineToApplication|AppHostOnly|Everywhere] [Level where it can be set]
+            overrideModeDefault="Allow"  [Allow|Deny] [Default delegation mode]
+            allowLocation="true"  [true|false] [Allowed in location tags]
+        />
+        
+        The recommended way to unlock sections is by using a location tag:
+        <location path="Default Web Site" overrideMode="Allow">
+            <system.webServer>
+                <asp />
+            </system.webServer>
+        </location>
+
+    -->
+    <configSections>
+        <sectionGroup name="system.applicationHost">
+            <section name="applicationPools" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
+            <section name="configHistory" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
+            <section name="customMetadata" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
+            <section name="listenerAdapters" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
+            <section name="log" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
+            <section name="serviceAutoStartProviders" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
+            <section name="sites" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
+            <section name="webLimits" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
+        </sectionGroup>
+
+        <sectionGroup name="system.webServer">
+            <section name="asp" overrideModeDefault="Deny" />
+            <section name="caching" overrideModeDefault="Allow" />
+            <section name="cgi" overrideModeDefault="Deny" />
+            <section name="defaultDocument" overrideModeDefault="Allow" />
+            <section name="directoryBrowse" overrideModeDefault="Allow" />
+            <section name="fastCgi" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
+            <section name="globalModules" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
+            <section name="handlers" overrideModeDefault="Deny" />
+            <section name="httpCompression" overrideModeDefault="Allow" allowDefinition="Everywhere" />
+            <section name="httpErrors" overrideModeDefault="Allow" />
+            <section name="httpLogging" overrideModeDefault="Deny" />
+            <section name="httpProtocol" overrideModeDefault="Allow" />
+            <section name="httpRedirect" overrideModeDefault="Allow" />
+            <section name="httpTracing" overrideModeDefault="Deny" />
+            <section name="isapiFilters" allowDefinition="MachineToApplication" overrideModeDefault="Deny" />
+            <section name="modules" allowDefinition="MachineToApplication" overrideModeDefault="Deny" />
+            <section name="applicationInitialization" allowDefinition="MachineToApplication" overrideModeDefault="Allow" />
+            <section name="odbcLogging" overrideModeDefault="Deny" />
+            <sectionGroup name="security">
+                <section name="access" overrideModeDefault="Deny" />
+                <section name="applicationDependencies" overrideModeDefault="Deny" />
+                <sectionGroup name="authentication">
+                    <section name="anonymousAuthentication" overrideModeDefault="Deny" />
+                    <section name="basicAuthentication" overrideModeDefault="Deny" />
+                    <section name="clientCertificateMappingAuthentication" overrideModeDefault="Deny" />
+                    <section name="digestAuthentication" overrideModeDefault="Deny" />
+                    <section name="iisClientCertificateMappingAuthentication" overrideModeDefault="Deny" />
+                    <section name="windowsAuthentication" overrideModeDefault="Deny" />
+                </sectionGroup>
+                <section name="authorization" overrideModeDefault="Allow" />
+                <section name="ipSecurity" overrideModeDefault="Deny" />
+                <section name="dynamicIpSecurity" overrideModeDefault="Deny" />
+                <section name="isapiCgiRestriction" allowDefinition="AppHostOnly" overrideModeDefault="Deny" />
+                <section name="requestFiltering" overrideModeDefault="Allow" />
+            </sectionGroup>
+            <section name="serverRuntime" overrideModeDefault="Deny" />
+            <section name="serverSideInclude" overrideModeDefault="Deny" />
+            <section name="staticContent" overrideModeDefault="Allow" />
+            <sectionGroup name="tracing">
+                <section name="traceFailedRequests" overrideModeDefault="Allow" />
+                <section name="traceProviderDefinitions" overrideModeDefault="Deny" />
+            </sectionGroup>
+            <section name="urlCompression" overrideModeDefault="Allow" />
+            <section name="validation" overrideModeDefault="Allow" />
+            <sectionGroup name="webdav">
+                <section name="globalSettings" overrideModeDefault="Deny" />
+                <section name="authoring" overrideModeDefault="Deny" />
+                <section name="authoringRules" overrideModeDefault="Deny" />
+            </sectionGroup>
+            <sectionGroup name="rewrite">
+                <section name="allowedServerVariables" overrideModeDefault="Deny" />
+                <section name="rules" overrideModeDefault="Allow" />
+                <section name="outboundRules" overrideModeDefault="Allow" />
+                <section name="globalRules" overrideModeDefault="Deny" allowDefinition="AppHostOnly" />
+                <section name="providers" overrideModeDefault="Allow" />
+                <section name="rewriteMaps" overrideModeDefault="Allow" />
+            </sectionGroup>
+            <section name="webSocket" overrideModeDefault="Deny" />
+        <section name="aspNetCore" overrideModeDefault="Allow" /></sectionGroup>
+    </configSections>
+
+    <configProtectedData>
+        <providers>
+            <add name="IISWASOnlyRsaProvider" type="" description="Uses RsaCryptoServiceProvider to encrypt and decrypt" keyContainerName="iisWasKey" cspProviderName="" useMachineContainer="true" useOAEP="false" />
+            <add name="AesProvider" type="Microsoft.ApplicationHost.AesProtectedConfigurationProvider" description="Uses an AES session key to encrypt and decrypt" keyContainerName="iisConfigurationKey" cspProviderName="" useOAEP="false" useMachineContainer="true" sessionKey="AQIAAA5mAAAApAAA/HKxkz6alrlAPez0IUgujj/6k3WxCDriHp6jvpv3yEZmo7h6SMzGLxo4mTrIQVHSkB7tmElHKfUFTzE2BWF7nFWHY6Z6qmGBauFzwJMwESjril7Gjz69RBFH259HQ6aRDq9Xfx7U7H4HtdmnKNqGjgl/hwPQBGeIlWiDh+sYv3vKB0QU971tjX6H2B+9armlnC8UOuA6JYMDMI/VLLL16sng0fWAy5JYe0YVABVjiAWDW264RZW9Tr1Oax4qHZKg+SdjULxeOc2YmpX+d0yeITo1HkPF1hN1gHpIPIUDo05ilHUNfR3OkjVCIQK4cFKCq1s8NH+y+13MxUC4Fn1AlQ==" />
+            <add name="IISWASOnlyAesProvider" type="Microsoft.ApplicationHost.AesProtectedConfigurationProvider" description="Uses an AES session key to encrypt and decrypt" keyContainerName="iisWasKey" cspProviderName="" useOAEP="false" useMachineContainer="true" sessionKey="AQIAAA5mAAAApAAALmU8lTC+v2qtfQiiiquvvLpUQqKLEXs+jSKoWCM/uPhyB++k4dwug19mGidNK5FYiWK2KYE1yhjVJcbp12E98Q0R2nT7eBiCMY2JairxQ591rqABK7keGaIjwH7PwGzSpILl3RJ4YFvJ/7ZXEJxeDZIjW8ZxWVXx+/VyHs9U3WguLEkgMUX3jrxJi8LouxaIVPJAv/YQ1ZCWs8zImitxX/C/7o7yaIxznfsN5nGQzQfpUDPeby99aw2zPVTtZI2LaWIBON8guABvZ6JtJVDWmfdK6sodbnwdZkr6/Z2rfvamT1dC1SpQrGG7ulR/f9/GXvCaW10ZVKxekBF/CYlNMg==" />
+        </providers>
+    </configProtectedData>
+
+    <system.applicationHost>
+
+        <applicationPools>
+            <add name="Clr4IntegratedAppPool" managedRuntimeVersion="v4.0" managedPipelineMode="Integrated" CLRConfigFile="%IIS_USER_HOME%\config\aspnet.config" autoStart="true" />
+            <add name="Clr4ClassicAppPool" managedRuntimeVersion="v4.0" managedPipelineMode="Classic" CLRConfigFile="%IIS_USER_HOME%\config\aspnet.config" autoStart="true" />
+            <add name="Clr2IntegratedAppPool" managedRuntimeVersion="v2.0" managedPipelineMode="Integrated" CLRConfigFile="%IIS_USER_HOME%\config\aspnet.config" autoStart="true" />
+            <add name="Clr2ClassicAppPool" managedRuntimeVersion="v2.0" managedPipelineMode="Classic" CLRConfigFile="%IIS_USER_HOME%\config\aspnet.config" autoStart="true" />
+            <add name="UnmanagedClassicAppPool" managedRuntimeVersion="" managedPipelineMode="Classic" autoStart="true" />
+            <applicationPoolDefaults managedRuntimeVersion="v4.0">
+                <processModel loadUserProfile="true" setProfileEnvironment="false" />
+            </applicationPoolDefaults>
+        </applicationPools>
+
+        <!--
+
+          The <listenerAdapters> section defines the protocols with which the
+          Windows Process Activation Service (WAS) binds.
+
+        -->
+        <listenerAdapters>
+            <add name="http" />
+        </listenerAdapters>
+
+        <sites>
+            <site name="WebSite1" id="1" serverAutoStart="true">
+                <application path="/">
+                    <virtualDirectory path="/" physicalPath="%IIS_SITES_HOME%\WebSite1" />
+                </application>
+                <bindings>
+                    <binding protocol="http" bindingInformation=":8080:localhost" />
+                </bindings>
+            </site>
+            <siteDefaults>
+                <!-- To enable logging, please change the below attribute "enabled" to "true" -->
+                <logFile logFormat="W3C" directory="%AppData%\Microsoft\IISExpressLogs" enabled="false" />
+                <traceFailedRequestsLogging directory="%AppData%\Microsoft" enabled="false" maxLogFileSizeKB="1024" />
+            </siteDefaults>
+            <applicationDefaults applicationPool="Clr4IntegratedAppPool" />
+            <virtualDirectoryDefaults allowSubDirConfig="true" />
+        </sites>
+
+        <webLimits />
+
+    </system.applicationHost>
+
+    <system.webServer>
+
+        <serverRuntime />
+
+        <asp scriptErrorSentToBrowser="true">
+            <cache diskTemplateCacheDirectory="%TEMP%\iisexpress\ASP Compiled Templates" />
+            <limits />
+        </asp>
+
+        <caching enabled="true" enableKernelCache="true">
+        </caching>
+
+        <cgi />
+
+        <defaultDocument enabled="true">
+            <files>
+                <add value="Default.htm" />
+                <add value="Default.asp" />
+                <add value="index.htm" />
+                <add value="index.html" />
+                <add value="iisstart.htm" />
+                <add value="default.aspx" />
+            </files>
+        </defaultDocument>
+
+        <directoryBrowse enabled="false" />
+
+        <fastCgi />
+
+        <!--
+
+          The <globalModules> section defines all native-code modules.
+          To enable a module, specify it in the <modules> section.
+
+        -->
+        <globalModules>
+            <add name="HttpLoggingModule" image="%IIS_BIN%\loghttp.dll" />
+            <add name="UriCacheModule" image="%IIS_BIN%\cachuri.dll" />
+            <add name="TokenCacheModule" image="%IIS_BIN%\cachtokn.dll" />
+            <add name="DynamicCompressionModule" image="%IIS_BIN%\compdyn.dll" />
+            <add name="StaticCompressionModule" image="%IIS_BIN%\compstat.dll" />
+            <add name="DefaultDocumentModule" image="%IIS_BIN%\defdoc.dll" />
+            <add name="DirectoryListingModule" image="%IIS_BIN%\dirlist.dll" />
+            <add name="ProtocolSupportModule" image="%IIS_BIN%\protsup.dll" />
+            <add name="HttpRedirectionModule" image="%IIS_BIN%\redirect.dll" />
+            <add name="ServerSideIncludeModule" image="%IIS_BIN%\iis_ssi.dll" />
+            <add name="StaticFileModule" image="%IIS_BIN%\static.dll" />
+            <add name="AnonymousAuthenticationModule" image="%IIS_BIN%\authanon.dll" />
+            <add name="CertificateMappingAuthenticationModule" image="%IIS_BIN%\authcert.dll" />
+            <add name="UrlAuthorizationModule" image="%IIS_BIN%\urlauthz.dll" />
+            <add name="BasicAuthenticationModule" image="%IIS_BIN%\authbas.dll" />
+            <add name="WindowsAuthenticationModule" image="%IIS_BIN%\authsspi.dll" />
+            <add name="IISCertificateMappingAuthenticationModule" image="%IIS_BIN%\authmap.dll" />
+            <add name="IpRestrictionModule" image="%IIS_BIN%\iprestr.dll" />
+            <add name="DynamicIpRestrictionModule" image="%IIS_BIN%\diprestr.dll" />
+            <add name="RequestFilteringModule" image="%IIS_BIN%\modrqflt.dll" />
+            <add name="CustomLoggingModule" image="%IIS_BIN%\logcust.dll" />
+            <add name="CustomErrorModule" image="%IIS_BIN%\custerr.dll" />
+            <add name="FailedRequestsTracingModule" image="%IIS_BIN%\iisfreb.dll" />
+            <add name="RequestMonitorModule" image="%IIS_BIN%\iisreqs.dll" />
+            <add name="IsapiModule" image="%IIS_BIN%\isapi.dll" />
+            <add name="IsapiFilterModule" image="%IIS_BIN%\filter.dll" />
+            <add name="CgiModule" image="%IIS_BIN%\cgi.dll" />
+            <add name="FastCgiModule" image="%IIS_BIN%\iisfcgi.dll" />
+<!--            <add name="WebDAVModule" image="%IIS_BIN%\webdav.dll" /> -->
+            <add name="RewriteModule" image="%IIS_BIN%\rewrite.dll" />
+            <add name="ConfigurationValidationModule" image="%IIS_BIN%\validcfg.dll" />
+            <add name="WebSocketModule" image="%IIS_BIN%\iiswsock.dll" />
+            <add name="WebMatrixSupportModule" image="%IIS_BIN%\webmatrixsup.dll" />
+            <add name="ManagedEngine" image="%windir%\Microsoft.NET\Framework\v2.0.50727\webengine.dll" preCondition="integratedMode,runtimeVersionv2.0,bitness32" />
+            <add name="ManagedEngine64" image="%windir%\Microsoft.NET\Framework64\v2.0.50727\webengine.dll" preCondition="integratedMode,runtimeVersionv2.0,bitness64" />
+            <add name="ManagedEngineV4.0_32bit" image="%windir%\Microsoft.NET\Framework\v4.0.30319\webengine4.dll" preCondition="integratedMode,runtimeVersionv4.0,bitness32" />
+            <add name="ManagedEngineV4.0_64bit" image="%windir%\Microsoft.NET\Framework64\v4.0.30319\webengine4.dll" preCondition="integratedMode,runtimeVersionv4.0,bitness64" />
+            <add name="ApplicationInitializationModule" image="%IIS_BIN%\warmup.dll" />
+            <add name="AspNetCoreModule" image="%IIS_BIN%\aspnetcore.dll" />
+            <add name="AspNetCoreModuleV2" image="%IIS_BIN%\Asp.Net Core Module\V2\aspnetcorev2.dll" />
+        </globalModules>
+
+        <httpCompression directory="%TEMP%">
+            <scheme name="gzip" dll="%IIS_BIN%\gzip.dll" />
+            <dynamicTypes>
+                <add mimeType="text/*" enabled="true" />
+                <add mimeType="message/*" enabled="true" />
+                <add mimeType="application/x-javascript" enabled="true" />
+                <add mimeType="application/javascript" enabled="true" />
+                <add mimeType="*/*" enabled="false" />
+                <add mimeType="text/event-stream" enabled="false" />
+            </dynamicTypes>
+            <staticTypes>
+                <add mimeType="text/*" enabled="true" />
+                <add mimeType="message/*" enabled="true" />
+                <add mimeType="application/javascript" enabled="true" />
+                <add mimeType="application/atom+xml" enabled="true" />
+                <add mimeType="application/xaml+xml" enabled="true" />
+                <add mimeType="image/svg+xml" enabled="true" />
+                <add mimeType="*/*" enabled="false" />
+            </staticTypes>
+        </httpCompression>
+
+        <httpErrors lockAttributes="allowAbsolutePathsWhenDelegated,defaultPath">
+            <error statusCode="401" prefixLanguageFilePath="%IIS_BIN%\custerr" path="401.htm" />
+            <error statusCode="403" prefixLanguageFilePath="%IIS_BIN%\custerr" path="403.htm" />
+            <error statusCode="404" prefixLanguageFilePath="%IIS_BIN%\custerr" path="404.htm" />
+            <error statusCode="405" prefixLanguageFilePath="%IIS_BIN%\custerr" path="405.htm" />
+            <error statusCode="406" prefixLanguageFilePath="%IIS_BIN%\custerr" path="406.htm" />
+            <error statusCode="412" prefixLanguageFilePath="%IIS_BIN%\custerr" path="412.htm" />
+            <error statusCode="500" prefixLanguageFilePath="%IIS_BIN%\custerr" path="500.htm" />
+            <error statusCode="501" prefixLanguageFilePath="%IIS_BIN%\custerr" path="501.htm" />
+            <error statusCode="502" prefixLanguageFilePath="%IIS_BIN%\custerr" path="502.htm" />
+        </httpErrors>
+
+        <httpLogging dontLog="false" />
+
+        <httpProtocol>
+            <customHeaders>
+                <clear />
+                <add name="X-Powered-By" value="ASP.NET" />
+            </customHeaders>
+            <redirectHeaders>
+                <clear />
+            </redirectHeaders>
+        </httpProtocol>
+
+        <httpRedirect enabled="false" />
+
+        <httpTracing />
+
+        <isapiFilters>
+            <filter name="ASP.Net_2.0.50727-64" path="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_filter.dll" enableCache="true" preCondition="bitness64,runtimeVersionv2.0" />
+            <filter name="ASP.Net_2.0.50727.0" path="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_filter.dll" enableCache="true" preCondition="bitness32,runtimeVersionv2.0" />
+            <filter name="ASP.Net_2.0_for_v1.1" path="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_filter.dll" enableCache="true" preCondition="runtimeVersionv1.1" />
+            <filter name="ASP.Net_4.0_32bit" path="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_filter.dll" enableCache="true" preCondition="bitness32,runtimeVersionv4.0" />
+            <filter name="ASP.Net_4.0_64bit" path="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_filter.dll" enableCache="true" preCondition="bitness64,runtimeVersionv4.0" />
+        </isapiFilters>
+
+        <odbcLogging />
+
+        <security>
+
+            <access sslFlags="None" />
+
+            <applicationDependencies>
+                <application name="Active Server Pages" groupId="ASP" />
+            </applicationDependencies>
+
+            <authentication>
+
+                <anonymousAuthentication enabled="true" userName="" />
+
+                <basicAuthentication enabled="false" />
+
+                <clientCertificateMappingAuthentication enabled="false" />
+
+                <digestAuthentication enabled="false" />
+
+                <iisClientCertificateMappingAuthentication enabled="false">
+                </iisClientCertificateMappingAuthentication>
+
+                <windowsAuthentication enabled="false">
+                    <providers>
+                        <add value="Negotiate" />
+                        <add value="NTLM" />
+                    </providers>
+                </windowsAuthentication>
+
+            </authentication>
+
+            <authorization>
+                <add accessType="Allow" users="*" />
+            </authorization>
+
+            <ipSecurity allowUnlisted="true" />
+
+            <isapiCgiRestriction notListedIsapisAllowed="true" notListedCgisAllowed="true">
+                <add path="%windir%\Microsoft.NET\Framework64\v4.0.30319\webengine4.dll" allowed="true" groupId="ASP.NET_v4.0" description="ASP.NET_v4.0" />
+                <add path="%windir%\Microsoft.NET\Framework\v4.0.30319\webengine4.dll" allowed="true" groupId="ASP.NET_v4.0" description="ASP.NET_v4.0" />
+                <add path="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" allowed="true" groupId="ASP.NET v2.0.50727" description="ASP.NET v2.0.50727" />
+                <add path="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" allowed="true" groupId="ASP.NET v2.0.50727" description="ASP.NET v2.0.50727" />
+            </isapiCgiRestriction>
+
+            <requestFiltering>
+                <fileExtensions allowUnlisted="true" applyToWebDAV="true">
+                    <add fileExtension=".asa" allowed="false" />
+                    <add fileExtension=".asax" allowed="false" />
+                    <add fileExtension=".ascx" allowed="false" />
+                    <add fileExtension=".master" allowed="false" />
+                    <add fileExtension=".skin" allowed="false" />
+                    <add fileExtension=".browser" allowed="false" />
+                    <add fileExtension=".sitemap" allowed="false" />
+                    <add fileExtension=".config" allowed="false" />
+                    <add fileExtension=".cs" allowed="false" />
+                    <add fileExtension=".csproj" allowed="false" />
+                    <add fileExtension=".vb" allowed="false" />
+                    <add fileExtension=".vbproj" allowed="false" />
+                    <add fileExtension=".webinfo" allowed="false" />
+                    <add fileExtension=".licx" allowed="false" />
+                    <add fileExtension=".resx" allowed="false" />
+                    <add fileExtension=".resources" allowed="false" />
+                    <add fileExtension=".mdb" allowed="false" />
+                    <add fileExtension=".vjsproj" allowed="false" />
+                    <add fileExtension=".java" allowed="false" />
+                    <add fileExtension=".jsl" allowed="false" />
+                    <add fileExtension=".ldb" allowed="false" />
+                    <add fileExtension=".dsdgm" allowed="false" />
+                    <add fileExtension=".ssdgm" allowed="false" />
+                    <add fileExtension=".lsad" allowed="false" />
+                    <add fileExtension=".ssmap" allowed="false" />
+                    <add fileExtension=".cd" allowed="false" />
+                    <add fileExtension=".dsprototype" allowed="false" />
+                    <add fileExtension=".lsaprototype" allowed="false" />
+                    <add fileExtension=".sdm" allowed="false" />
+                    <add fileExtension=".sdmDocument" allowed="false" />
+                    <add fileExtension=".mdf" allowed="false" />
+                    <add fileExtension=".ldf" allowed="false" />
+                    <add fileExtension=".ad" allowed="false" />
+                    <add fileExtension=".dd" allowed="false" />
+                    <add fileExtension=".ldd" allowed="false" />
+                    <add fileExtension=".sd" allowed="false" />
+                    <add fileExtension=".adprototype" allowed="false" />
+                    <add fileExtension=".lddprototype" allowed="false" />
+                    <add fileExtension=".exclude" allowed="false" />
+                    <add fileExtension=".refresh" allowed="false" />
+                    <add fileExtension=".compiled" allowed="false" />
+                    <add fileExtension=".msgx" allowed="false" />
+                    <add fileExtension=".vsdisco" allowed="false" />
+                    <add fileExtension=".rules" allowed="false" />
+                </fileExtensions>
+                <verbs allowUnlisted="true" applyToWebDAV="true" />
+                <hiddenSegments applyToWebDAV="true">
+                    <add segment="web.config" />
+                    <add segment="bin" />
+                    <add segment="App_code" />
+                    <add segment="App_GlobalResources" />
+                    <add segment="App_LocalResources" />
+                    <add segment="App_WebReferences" />
+                    <add segment="App_Data" />
+                    <add segment="App_Browsers" />
+                </hiddenSegments>
+            </requestFiltering>
+
+        </security>
+
+        <serverSideInclude ssiExecDisable="false" />
+
+        <staticContent lockAttributes="isDocFooterFileName">
+            <mimeMap fileExtension=".323" mimeType="text/h323" />
+            <mimeMap fileExtension=".3g2" mimeType="video/3gpp2" />
+            <mimeMap fileExtension=".3gp2" mimeType="video/3gpp2" />
+            <mimeMap fileExtension=".3gp" mimeType="video/3gpp" />
+            <mimeMap fileExtension=".3gpp" mimeType="video/3gpp" />
+            <mimeMap fileExtension=".aac" mimeType="audio/aac" />
+            <mimeMap fileExtension=".aaf" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".aca" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".accdb" mimeType="application/msaccess" />
+            <mimeMap fileExtension=".accde" mimeType="application/msaccess" />
+            <mimeMap fileExtension=".accdt" mimeType="application/msaccess" />
+            <mimeMap fileExtension=".acx" mimeType="application/internet-property-stream" />
+            <mimeMap fileExtension=".adt" mimeType="audio/vnd.dlna.adts" />
+            <mimeMap fileExtension=".adts" mimeType="audio/vnd.dlna.adts" />
+            <mimeMap fileExtension=".afm" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".ai" mimeType="application/postscript" />
+            <mimeMap fileExtension=".aif" mimeType="audio/x-aiff" />
+            <mimeMap fileExtension=".aifc" mimeType="audio/aiff" />
+            <mimeMap fileExtension=".aiff" mimeType="audio/aiff" />
+            <mimeMap fileExtension=".appcache" mimeType="text/cache-manifest" />
+            <mimeMap fileExtension=".application" mimeType="application/x-ms-application" />
+            <mimeMap fileExtension=".art" mimeType="image/x-jg" />
+            <mimeMap fileExtension=".asd" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".asf" mimeType="video/x-ms-asf" />
+            <mimeMap fileExtension=".asi" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".asm" mimeType="text/plain" />
+            <mimeMap fileExtension=".asr" mimeType="video/x-ms-asf" />
+            <mimeMap fileExtension=".asx" mimeType="video/x-ms-asf" />
+            <mimeMap fileExtension=".atom" mimeType="application/atom+xml" />
+            <mimeMap fileExtension=".au" mimeType="audio/basic" />
+            <mimeMap fileExtension=".avi" mimeType="video/avi" />
+            <mimeMap fileExtension=".axs" mimeType="application/olescript" />
+            <mimeMap fileExtension=".bas" mimeType="text/plain" />
+            <mimeMap fileExtension=".bcpio" mimeType="application/x-bcpio" />
+            <mimeMap fileExtension=".bin" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".bmp" mimeType="image/bmp" />
+            <mimeMap fileExtension=".c" mimeType="text/plain" />
+            <mimeMap fileExtension=".cab" mimeType="application/vnd.ms-cab-compressed" />
+            <mimeMap fileExtension=".calx" mimeType="application/vnd.ms-office.calx" />
+            <mimeMap fileExtension=".cat" mimeType="application/vnd.ms-pki.seccat" />
+            <mimeMap fileExtension=".cdf" mimeType="application/x-cdf" />
+            <mimeMap fileExtension=".chm" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".class" mimeType="application/x-java-applet" />
+            <mimeMap fileExtension=".clp" mimeType="application/x-msclip" />
+            <mimeMap fileExtension=".cmx" mimeType="image/x-cmx" />
+            <mimeMap fileExtension=".cnf" mimeType="text/plain" />
+            <mimeMap fileExtension=".cod" mimeType="image/cis-cod" />
+            <mimeMap fileExtension=".cpio" mimeType="application/x-cpio" />
+            <mimeMap fileExtension=".cpp" mimeType="text/plain" />
+            <mimeMap fileExtension=".crd" mimeType="application/x-mscardfile" />
+            <mimeMap fileExtension=".crl" mimeType="application/pkix-crl" />
+            <mimeMap fileExtension=".crt" mimeType="application/x-x509-ca-cert" />
+            <mimeMap fileExtension=".csh" mimeType="application/x-csh" />
+            <mimeMap fileExtension=".css" mimeType="text/css" />
+            <mimeMap fileExtension=".csv" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".cur" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".dcr" mimeType="application/x-director" />
+            <mimeMap fileExtension=".deploy" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".der" mimeType="application/x-x509-ca-cert" />
+            <mimeMap fileExtension=".dib" mimeType="image/bmp" />
+            <mimeMap fileExtension=".dir" mimeType="application/x-director" />
+            <mimeMap fileExtension=".disco" mimeType="text/xml" />
+            <mimeMap fileExtension=".dll" mimeType="application/x-msdownload" />
+            <mimeMap fileExtension=".dll.config" mimeType="text/xml" />
+            <mimeMap fileExtension=".dlm" mimeType="text/dlm" />
+            <mimeMap fileExtension=".doc" mimeType="application/msword" />
+            <mimeMap fileExtension=".docm" mimeType="application/vnd.ms-word.document.macroEnabled.12" />
+            <mimeMap fileExtension=".docx" mimeType="application/vnd.openxmlformats-officedocument.wordprocessingml.document" />
+            <mimeMap fileExtension=".dot" mimeType="application/msword" />
+            <mimeMap fileExtension=".dotm" mimeType="application/vnd.ms-word.template.macroEnabled.12" />
+            <mimeMap fileExtension=".dotx" mimeType="application/vnd.openxmlformats-officedocument.wordprocessingml.template" />
+            <mimeMap fileExtension=".dsp" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".dtd" mimeType="text/xml" />
+            <mimeMap fileExtension=".dvi" mimeType="application/x-dvi" />
+            <mimeMap fileExtension=".dvr-ms" mimeType="video/x-ms-dvr" />
+            <mimeMap fileExtension=".dwf" mimeType="drawing/x-dwf" />
+            <mimeMap fileExtension=".dwp" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".dxr" mimeType="application/x-director" />
+            <mimeMap fileExtension=".eml" mimeType="message/rfc822" />
+            <mimeMap fileExtension=".emz" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".eot" mimeType="application/vnd.ms-fontobject" />
+            <mimeMap fileExtension=".eps" mimeType="application/postscript" />
+            <mimeMap fileExtension=".esd" mimeType="application/vnd.ms-cab-compressed" />
+            <mimeMap fileExtension=".etx" mimeType="text/x-setext" />
+            <mimeMap fileExtension=".evy" mimeType="application/envoy" />
+            <mimeMap fileExtension=".exe" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".exe.config" mimeType="text/xml" />
+            <mimeMap fileExtension=".fdf" mimeType="application/vnd.fdf" />
+            <mimeMap fileExtension=".fif" mimeType="application/fractals" />
+            <mimeMap fileExtension=".fla" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".flr" mimeType="x-world/x-vrml" />
+            <mimeMap fileExtension=".flv" mimeType="video/x-flv" />
+            <mimeMap fileExtension=".gif" mimeType="image/gif" />
+            <mimeMap fileExtension=".glb" mimeType="model/gltf-binary" />
+            <mimeMap fileExtension=".gtar" mimeType="application/x-gtar" />
+            <mimeMap fileExtension=".gz" mimeType="application/x-gzip" />
+            <mimeMap fileExtension=".h" mimeType="text/plain" />
+            <mimeMap fileExtension=".hdf" mimeType="application/x-hdf" />
+            <mimeMap fileExtension=".hdml" mimeType="text/x-hdml" />
+            <mimeMap fileExtension=".hhc" mimeType="application/x-oleobject" />
+            <mimeMap fileExtension=".hhk" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".hhp" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".hlp" mimeType="application/winhlp" />
+            <mimeMap fileExtension=".hqx" mimeType="application/mac-binhex40" />
+            <mimeMap fileExtension=".hta" mimeType="application/hta" />
+            <mimeMap fileExtension=".htc" mimeType="text/x-component" />
+            <mimeMap fileExtension=".htm" mimeType="text/html" />
+            <mimeMap fileExtension=".html" mimeType="text/html" />
+            <mimeMap fileExtension=".htt" mimeType="text/webviewhtml" />
+            <mimeMap fileExtension=".hxt" mimeType="text/html" />
+            <mimeMap fileExtension=".ico" mimeType="image/x-icon" />
+            <mimeMap fileExtension=".ics" mimeType="text/calendar" />
+            <mimeMap fileExtension=".ief" mimeType="image/ief" />
+            <mimeMap fileExtension=".iii" mimeType="application/x-iphone" />
+            <mimeMap fileExtension=".inf" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".ins" mimeType="application/x-internet-signup" />
+            <mimeMap fileExtension=".isp" mimeType="application/x-internet-signup" />
+            <mimeMap fileExtension=".IVF" mimeType="video/x-ivf" />
+            <mimeMap fileExtension=".jar" mimeType="application/java-archive" />
+            <mimeMap fileExtension=".java" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".jck" mimeType="application/liquidmotion" />
+            <mimeMap fileExtension=".jcz" mimeType="application/liquidmotion" />
+            <mimeMap fileExtension=".jfif" mimeType="image/pjpeg" />
+            <mimeMap fileExtension=".jpb" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".jpe" mimeType="image/jpeg" />
+            <mimeMap fileExtension=".jpeg" mimeType="image/jpeg" />
+            <mimeMap fileExtension=".jpg" mimeType="image/jpeg" />
+            <mimeMap fileExtension=".js" mimeType="application/javascript" />
+            <mimeMap fileExtension=".json" mimeType="application/json" />
+            <mimeMap fileExtension=".jsonld" mimeType="application/ld+json" />
+            <mimeMap fileExtension=".jsx" mimeType="text/jscript" />
+            <mimeMap fileExtension=".latex" mimeType="application/x-latex" />
+            <mimeMap fileExtension=".less" mimeType="text/css" />
+            <mimeMap fileExtension=".lit" mimeType="application/x-ms-reader" />
+            <mimeMap fileExtension=".lpk" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".lsf" mimeType="video/x-la-asf" />
+            <mimeMap fileExtension=".lsx" mimeType="video/x-la-asf" />
+            <mimeMap fileExtension=".lzh" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".m13" mimeType="application/x-msmediaview" />
+            <mimeMap fileExtension=".m14" mimeType="application/x-msmediaview" />
+            <mimeMap fileExtension=".m1v" mimeType="video/mpeg" />
+            <mimeMap fileExtension=".m2ts" mimeType="video/vnd.dlna.mpeg-tts" />
+            <mimeMap fileExtension=".m3u" mimeType="audio/x-mpegurl" />
+            <mimeMap fileExtension=".m4a" mimeType="audio/mp4" />
+            <mimeMap fileExtension=".m4v" mimeType="video/mp4" />
+            <mimeMap fileExtension=".man" mimeType="application/x-troff-man" />
+            <mimeMap fileExtension=".manifest" mimeType="application/x-ms-manifest" />
+            <mimeMap fileExtension=".map" mimeType="text/plain" />
+            <mimeMap fileExtension=".mdb" mimeType="application/x-msaccess" />
+            <mimeMap fileExtension=".mdp" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".me" mimeType="application/x-troff-me" />
+            <mimeMap fileExtension=".mht" mimeType="message/rfc822" />
+            <mimeMap fileExtension=".mhtml" mimeType="message/rfc822" />
+            <mimeMap fileExtension=".mid" mimeType="audio/mid" />
+            <mimeMap fileExtension=".midi" mimeType="audio/mid" />
+            <mimeMap fileExtension=".mix" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".mmf" mimeType="application/x-smaf" />
+            <mimeMap fileExtension=".mno" mimeType="text/xml" />
+            <mimeMap fileExtension=".mny" mimeType="application/x-msmoney" />
+            <mimeMap fileExtension=".mov" mimeType="video/quicktime" />
+            <mimeMap fileExtension=".movie" mimeType="video/x-sgi-movie" />
+            <mimeMap fileExtension=".mp2" mimeType="video/mpeg" />
+            <mimeMap fileExtension=".mp3" mimeType="audio/mpeg" />
+            <mimeMap fileExtension=".mp4" mimeType="video/mp4" />
+            <mimeMap fileExtension=".mp4v" mimeType="video/mp4" />
+            <mimeMap fileExtension=".mpa" mimeType="video/mpeg" />
+            <mimeMap fileExtension=".mpe" mimeType="video/mpeg" />
+            <mimeMap fileExtension=".mpeg" mimeType="video/mpeg" />
+            <mimeMap fileExtension=".mpg" mimeType="video/mpeg" />
+            <mimeMap fileExtension=".mpp" mimeType="application/vnd.ms-project" />
+            <mimeMap fileExtension=".mpv2" mimeType="video/mpeg" />
+            <mimeMap fileExtension=".ms" mimeType="application/x-troff-ms" />
+            <mimeMap fileExtension=".msi" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".mso" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".mvb" mimeType="application/x-msmediaview" />
+            <mimeMap fileExtension=".mvc" mimeType="application/x-miva-compiled" />
+            <mimeMap fileExtension=".nc" mimeType="application/x-netcdf" />
+            <mimeMap fileExtension=".nsc" mimeType="video/x-ms-asf" />
+            <mimeMap fileExtension=".nws" mimeType="message/rfc822" />
+            <mimeMap fileExtension=".ocx" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".oda" mimeType="application/oda" />
+            <mimeMap fileExtension=".odc" mimeType="text/x-ms-odc" />
+            <mimeMap fileExtension=".ods" mimeType="application/oleobject" />
+            <mimeMap fileExtension=".oga" mimeType="audio/ogg" />
+            <mimeMap fileExtension=".ogg" mimeType="video/ogg" />
+            <mimeMap fileExtension=".ogv" mimeType="video/ogg" />
+            <mimeMap fileExtension=".one" mimeType="application/onenote" />
+            <mimeMap fileExtension=".onea" mimeType="application/onenote" />
+            <mimeMap fileExtension=".onetoc" mimeType="application/onenote" />
+            <mimeMap fileExtension=".onetoc2" mimeType="application/onenote" />
+            <mimeMap fileExtension=".onetmp" mimeType="application/onenote" />
+            <mimeMap fileExtension=".onepkg" mimeType="application/onenote" />
+            <mimeMap fileExtension=".osdx" mimeType="application/opensearchdescription+xml" />
+            <mimeMap fileExtension=".otf" mimeType="font/otf" />
+            <mimeMap fileExtension=".p10" mimeType="application/pkcs10" />
+            <mimeMap fileExtension=".p12" mimeType="application/x-pkcs12" />
+            <mimeMap fileExtension=".p7b" mimeType="application/x-pkcs7-certificates" />
+            <mimeMap fileExtension=".p7c" mimeType="application/pkcs7-mime" />
+            <mimeMap fileExtension=".p7m" mimeType="application/pkcs7-mime" />
+            <mimeMap fileExtension=".p7r" mimeType="application/x-pkcs7-certreqresp" />
+            <mimeMap fileExtension=".p7s" mimeType="application/pkcs7-signature" />
+            <mimeMap fileExtension=".pbm" mimeType="image/x-portable-bitmap" />
+            <mimeMap fileExtension=".pcx" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".pcz" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".pdf" mimeType="application/pdf" />
+            <mimeMap fileExtension=".pfb" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".pfm" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".pfx" mimeType="application/x-pkcs12" />
+            <mimeMap fileExtension=".pgm" mimeType="image/x-portable-graymap" />
+            <mimeMap fileExtension=".pko" mimeType="application/vnd.ms-pki.pko" />
+            <mimeMap fileExtension=".pma" mimeType="application/x-perfmon" />
+            <mimeMap fileExtension=".pmc" mimeType="application/x-perfmon" />
+            <mimeMap fileExtension=".pml" mimeType="application/x-perfmon" />
+            <mimeMap fileExtension=".pmr" mimeType="application/x-perfmon" />
+            <mimeMap fileExtension=".pmw" mimeType="application/x-perfmon" />
+            <mimeMap fileExtension=".png" mimeType="image/png" />
+            <mimeMap fileExtension=".pnm" mimeType="image/x-portable-anymap" />
+            <mimeMap fileExtension=".pnz" mimeType="image/png" />
+            <mimeMap fileExtension=".pot" mimeType="application/vnd.ms-powerpoint" />
+            <mimeMap fileExtension=".potm" mimeType="application/vnd.ms-powerpoint.template.macroEnabled.12" />
+            <mimeMap fileExtension=".potx" mimeType="application/vnd.openxmlformats-officedocument.presentationml.template" />
+            <mimeMap fileExtension=".ppam" mimeType="application/vnd.ms-powerpoint.addin.macroEnabled.12" />
+            <mimeMap fileExtension=".ppm" mimeType="image/x-portable-pixmap" />
+            <mimeMap fileExtension=".pps" mimeType="application/vnd.ms-powerpoint" />
+            <mimeMap fileExtension=".ppsm" mimeType="application/vnd.ms-powerpoint.slideshow.macroEnabled.12" />
+            <mimeMap fileExtension=".ppsx" mimeType="application/vnd.openxmlformats-officedocument.presentationml.slideshow" />
+            <mimeMap fileExtension=".ppt" mimeType="application/vnd.ms-powerpoint" />
+            <mimeMap fileExtension=".pptm" mimeType="application/vnd.ms-powerpoint.presentation.macroEnabled.12" />
+            <mimeMap fileExtension=".pptx" mimeType="application/vnd.openxmlformats-officedocument.presentationml.presentation" />
+            <mimeMap fileExtension=".prf" mimeType="application/pics-rules" />
+            <mimeMap fileExtension=".prm" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".prx" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".ps" mimeType="application/postscript" />
+            <mimeMap fileExtension=".psd" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".psm" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".psp" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".pub" mimeType="application/x-mspublisher" />
+            <mimeMap fileExtension=".qt" mimeType="video/quicktime" />
+            <mimeMap fileExtension=".qtl" mimeType="application/x-quicktimeplayer" />
+            <mimeMap fileExtension=".qxd" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".ra" mimeType="audio/x-pn-realaudio" />
+            <mimeMap fileExtension=".ram" mimeType="audio/x-pn-realaudio" />
+            <mimeMap fileExtension=".rar" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".ras" mimeType="image/x-cmu-raster" />
+            <mimeMap fileExtension=".rf" mimeType="image/vnd.rn-realflash" />
+            <mimeMap fileExtension=".rgb" mimeType="image/x-rgb" />
+            <mimeMap fileExtension=".rm" mimeType="application/vnd.rn-realmedia" />
+            <mimeMap fileExtension=".rmi" mimeType="audio/mid" />
+            <mimeMap fileExtension=".roff" mimeType="application/x-troff" />
+            <mimeMap fileExtension=".rpm" mimeType="audio/x-pn-realaudio-plugin" />
+            <mimeMap fileExtension=".rtf" mimeType="application/rtf" />
+            <mimeMap fileExtension=".rtx" mimeType="text/richtext" />
+            <mimeMap fileExtension=".scd" mimeType="application/x-msschedule" />
+            <mimeMap fileExtension=".sct" mimeType="text/scriptlet" />
+            <mimeMap fileExtension=".sea" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".setpay" mimeType="application/set-payment-initiation" />
+            <mimeMap fileExtension=".setreg" mimeType="application/set-registration-initiation" />
+            <mimeMap fileExtension=".sgml" mimeType="text/sgml" />
+            <mimeMap fileExtension=".sh" mimeType="application/x-sh" />
+            <mimeMap fileExtension=".shar" mimeType="application/x-shar" />
+            <mimeMap fileExtension=".sit" mimeType="application/x-stuffit" />
+            <mimeMap fileExtension=".sldm" mimeType="application/vnd.ms-powerpoint.slide.macroEnabled.12" />
+            <mimeMap fileExtension=".sldx" mimeType="application/vnd.openxmlformats-officedocument.presentationml.slide" />
+            <mimeMap fileExtension=".smd" mimeType="audio/x-smd" />
+            <mimeMap fileExtension=".smi" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".smx" mimeType="audio/x-smd" />
+            <mimeMap fileExtension=".smz" mimeType="audio/x-smd" />
+            <mimeMap fileExtension=".snd" mimeType="audio/basic" />
+            <mimeMap fileExtension=".snp" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".spc" mimeType="application/x-pkcs7-certificates" />
+            <mimeMap fileExtension=".spl" mimeType="application/futuresplash" />
+            <mimeMap fileExtension=".spx" mimeType="audio/ogg" />
+            <mimeMap fileExtension=".src" mimeType="application/x-wais-source" />
+            <mimeMap fileExtension=".ssm" mimeType="application/streamingmedia" />
+            <mimeMap fileExtension=".sst" mimeType="application/vnd.ms-pki.certstore" />
+            <mimeMap fileExtension=".stl" mimeType="application/vnd.ms-pki.stl" />
+            <mimeMap fileExtension=".sv4cpio" mimeType="application/x-sv4cpio" />
+            <mimeMap fileExtension=".sv4crc" mimeType="application/x-sv4crc" />
+            <mimeMap fileExtension=".svg" mimeType="image/svg+xml" />
+            <mimeMap fileExtension=".svgz" mimeType="image/svg+xml" />
+            <mimeMap fileExtension=".swf" mimeType="application/x-shockwave-flash" />
+            <mimeMap fileExtension=".t" mimeType="application/x-troff" />
+            <mimeMap fileExtension=".tar" mimeType="application/x-tar" />
+            <mimeMap fileExtension=".tcl" mimeType="application/x-tcl" />
+            <mimeMap fileExtension=".tex" mimeType="application/x-tex" />
+            <mimeMap fileExtension=".texi" mimeType="application/x-texinfo" />
+            <mimeMap fileExtension=".texinfo" mimeType="application/x-texinfo" />
+            <mimeMap fileExtension=".tgz" mimeType="application/x-compressed" />
+            <mimeMap fileExtension=".thmx" mimeType="application/vnd.ms-officetheme" />
+            <mimeMap fileExtension=".thn" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".tif" mimeType="image/tiff" />
+            <mimeMap fileExtension=".tiff" mimeType="image/tiff" />
+            <mimeMap fileExtension=".toc" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".tr" mimeType="application/x-troff" />
+            <mimeMap fileExtension=".trm" mimeType="application/x-msterminal" />
+            <mimeMap fileExtension=".ts" mimeType="video/vnd.dlna.mpeg-tts" />
+            <mimeMap fileExtension=".tsv" mimeType="text/tab-separated-values" />
+            <mimeMap fileExtension=".ttf" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".tts" mimeType="video/vnd.dlna.mpeg-tts" />
+            <mimeMap fileExtension=".txt" mimeType="text/plain" />
+            <mimeMap fileExtension=".u32" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".uls" mimeType="text/iuls" />
+            <mimeMap fileExtension=".ustar" mimeType="application/x-ustar" />
+            <mimeMap fileExtension=".vbs" mimeType="text/vbscript" />
+            <mimeMap fileExtension=".vcf" mimeType="text/x-vcard" />
+            <mimeMap fileExtension=".vcs" mimeType="text/plain" />
+            <mimeMap fileExtension=".vdx" mimeType="application/vnd.ms-visio.viewer" />
+            <mimeMap fileExtension=".vml" mimeType="text/xml" />
+            <mimeMap fileExtension=".vsd" mimeType="application/vnd.visio" />
+            <mimeMap fileExtension=".vss" mimeType="application/vnd.visio" />
+            <mimeMap fileExtension=".vst" mimeType="application/vnd.visio" />
+            <mimeMap fileExtension=".vsto" mimeType="application/x-ms-vsto" />
+            <mimeMap fileExtension=".vsw" mimeType="application/vnd.visio" />
+            <mimeMap fileExtension=".vsx" mimeType="application/vnd.visio" />
+            <mimeMap fileExtension=".vtx" mimeType="application/vnd.visio" />
+            <mimeMap fileExtension=".wasm" mimeType="application/wasm" />
+            <mimeMap fileExtension=".wav" mimeType="audio/wav" />
+            <mimeMap fileExtension=".wax" mimeType="audio/x-ms-wax" />
+            <mimeMap fileExtension=".wbmp" mimeType="image/vnd.wap.wbmp" />
+            <mimeMap fileExtension=".wcm" mimeType="application/vnd.ms-works" />
+            <mimeMap fileExtension=".wdb" mimeType="application/vnd.ms-works" />
+            <mimeMap fileExtension=".webm" mimeType="video/webm" />
+            <mimeMap fileExtension=".wks" mimeType="application/vnd.ms-works" />
+            <mimeMap fileExtension=".wm" mimeType="video/x-ms-wm" />
+            <mimeMap fileExtension=".wma" mimeType="audio/x-ms-wma" />
+            <mimeMap fileExtension=".wmd" mimeType="application/x-ms-wmd" />
+            <mimeMap fileExtension=".wmf" mimeType="application/x-msmetafile" />
+            <mimeMap fileExtension=".wml" mimeType="text/vnd.wap.wml" />
+            <mimeMap fileExtension=".wmlc" mimeType="application/vnd.wap.wmlc" />
+            <mimeMap fileExtension=".wmls" mimeType="text/vnd.wap.wmlscript" />
+            <mimeMap fileExtension=".wmlsc" mimeType="application/vnd.wap.wmlscriptc" />
+            <mimeMap fileExtension=".wmp" mimeType="video/x-ms-wmp" />
+            <mimeMap fileExtension=".wmv" mimeType="video/x-ms-wmv" />
+            <mimeMap fileExtension=".wmx" mimeType="video/x-ms-wmx" />
+            <mimeMap fileExtension=".wmz" mimeType="application/x-ms-wmz" />
+            <mimeMap fileExtension=".woff" mimeType="font/x-woff" />
+            <mimeMap fileExtension=".woff2" mimeType="application/font-woff2" />
+            <mimeMap fileExtension=".wps" mimeType="application/vnd.ms-works" />
+            <mimeMap fileExtension=".wri" mimeType="application/x-mswrite" />
+            <mimeMap fileExtension=".wrl" mimeType="x-world/x-vrml" />
+            <mimeMap fileExtension=".wrz" mimeType="x-world/x-vrml" />
+            <mimeMap fileExtension=".wsdl" mimeType="text/xml" />
+            <mimeMap fileExtension=".wtv" mimeType="video/x-ms-wtv" />
+            <mimeMap fileExtension=".wvx" mimeType="video/x-ms-wvx" />
+            <mimeMap fileExtension=".x" mimeType="application/directx" />
+            <mimeMap fileExtension=".xaf" mimeType="x-world/x-vrml" />
+            <mimeMap fileExtension=".xaml" mimeType="application/xaml+xml" />
+            <mimeMap fileExtension=".xap" mimeType="application/x-silverlight-app" />
+            <mimeMap fileExtension=".xbap" mimeType="application/x-ms-xbap" />
+            <mimeMap fileExtension=".xbm" mimeType="image/x-xbitmap" />
+            <mimeMap fileExtension=".xdr" mimeType="text/plain" />
+            <mimeMap fileExtension=".xht" mimeType="application/xhtml+xml" />
+            <mimeMap fileExtension=".xhtml" mimeType="application/xhtml+xml" />
+            <mimeMap fileExtension=".xla" mimeType="application/vnd.ms-excel" />
+            <mimeMap fileExtension=".xlam" mimeType="application/vnd.ms-excel.addin.macroEnabled.12" />
+            <mimeMap fileExtension=".xlc" mimeType="application/vnd.ms-excel" />
+            <mimeMap fileExtension=".xlm" mimeType="application/vnd.ms-excel" />
+            <mimeMap fileExtension=".xls" mimeType="application/vnd.ms-excel" />
+            <mimeMap fileExtension=".xlsb" mimeType="application/vnd.ms-excel.sheet.binary.macroEnabled.12" />
+            <mimeMap fileExtension=".xlsm" mimeType="application/vnd.ms-excel.sheet.macroEnabled.12" />
+            <mimeMap fileExtension=".xlsx" mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" />
+            <mimeMap fileExtension=".xlt" mimeType="application/vnd.ms-excel" />
+            <mimeMap fileExtension=".xltm" mimeType="application/vnd.ms-excel.template.macroEnabled.12" />
+            <mimeMap fileExtension=".xltx" mimeType="application/vnd.openxmlformats-officedocument.spreadsheetml.template" />
+            <mimeMap fileExtension=".xlw" mimeType="application/vnd.ms-excel" />
+            <mimeMap fileExtension=".xml" mimeType="text/xml" />
+            <mimeMap fileExtension=".xof" mimeType="x-world/x-vrml" />
+            <mimeMap fileExtension=".xpm" mimeType="image/x-xpixmap" />
+            <mimeMap fileExtension=".xps" mimeType="application/vnd.ms-xpsdocument" />
+            <mimeMap fileExtension=".xsd" mimeType="text/xml" />
+            <mimeMap fileExtension=".xsf" mimeType="text/xml" />
+            <mimeMap fileExtension=".xsl" mimeType="text/xml" />
+            <mimeMap fileExtension=".xslt" mimeType="text/xml" />
+            <mimeMap fileExtension=".xsn" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".xtp" mimeType="application/octet-stream" />
+            <mimeMap fileExtension=".xwd" mimeType="image/x-xwindowdump" />
+            <mimeMap fileExtension=".z" mimeType="application/x-compress" />
+            <mimeMap fileExtension=".zip" mimeType="application/x-zip-compressed" />
+        </staticContent>
+
+        <tracing>
+
+            <traceFailedRequests>
+                <add path="*">
+                    <traceAreas>
+                        <add provider="ASP" verbosity="Verbose" />
+                        <add provider="ASPNET" areas="Infrastructure,Module,Page,AppServices" verbosity="Verbose" />
+                        <add provider="ISAPI Extension" verbosity="Verbose" />
+                        <add provider="WWW Server" areas="Authentication,Security,Filter,StaticFile,CGI,Compression,Cache,RequestNotifications,Module,Rewrite,WebSocket" verbosity="Verbose" />
+                    </traceAreas>
+                    <failureDefinitions statusCodes="200-999" />
+                </add>
+            </traceFailedRequests>
+
+             <traceProviderDefinitions>
+                <add name="WWW Server" guid="{3a2a4e84-4c21-4981-ae10-3fda0d9b0f83}">
+                    <areas>
+                        <clear />
+                        <add name="Authentication" value="2" />
+                        <add name="Security" value="4" />
+                        <add name="Filter" value="8" />
+                        <add name="StaticFile" value="16" />
+                        <add name="CGI" value="32" />
+                        <add name="Compression" value="64" />
+                        <add name="Cache" value="128" />
+                        <add name="RequestNotifications" value="256" />
+                        <add name="Module" value="512" />
+                        <add name="Rewrite" value="1024" />
+                        <add name="FastCGI" value="4096" />
+                        <add name="WebSocket" value="16384" />
+                        <add name="ANCM" value="65536" />
+                    </areas>
+                </add>
+                <add name="ASP" guid="{06b94d9a-b15e-456e-a4ef-37c984a2cb4b}">
+                    <areas>
+                        <clear />
+                    </areas>
+                </add>
+                <add name="ISAPI Extension" guid="{a1c2040e-8840-4c31-ba11-9871031a19ea}">
+                    <areas>
+                        <clear />
+                    </areas>
+                </add>
+                <add name="ASPNET" guid="{AFF081FE-0247-4275-9C4E-021F3DC1DA35}">
+                    <areas>
+                        <add name="Infrastructure" value="1" />
+                        <add name="Module" value="2" />
+                        <add name="Page" value="4" />
+                        <add name="AppServices" value="8" />
+                    </areas>
+                </add>
+            </traceProviderDefinitions>
+
+        </tracing>
+
+        <urlCompression />
+
+        <validation />
+        <webdav>
+            <globalSettings>
+                <propertyStores>
+                    <add name="webdav_simple_prop" image="%IIS_BIN%\webdav_simple_prop.dll" image32="%IIS_BIN%\webdav_simple_prop.dll" />
+                </propertyStores>
+                <lockStores>
+                    <add name="webdav_simple_lock" image="%IIS_BIN%\webdav_simple_lock.dll" image32="%IIS_BIN%\webdav_simple_lock.dll" />
+                </lockStores>
+
+            </globalSettings>
+            <authoring>
+                <locks enabled="true" lockStore="webdav_simple_lock" />
+            </authoring>
+            <authoringRules />
+        </webdav>
+        <webSocket />
+        <applicationInitialization />
+
+    </system.webServer>
+    <location path="" overrideMode="Allow">
+        <system.webServer>
+            <modules>
+                <add name="IsapiFilterModule" lockItem="true" />
+                <add name="BasicAuthenticationModule" lockItem="true" />
+                <add name="IsapiModule" lockItem="true" />
+                <add name="HttpLoggingModule" lockItem="true" />
+                <add name="DynamicCompressionModule" lockItem="true" />
+                <add name="StaticCompressionModule" lockItem="true" />
+                <add name="DefaultDocumentModule" lockItem="true" />
+                <add name="DirectoryListingModule" lockItem="true" />
+                <add name="ProtocolSupportModule" lockItem="true" />
+                <add name="HttpRedirectionModule" lockItem="true" />
+                <add name="ServerSideIncludeModule" lockItem="true" />
+                <add name="StaticFileModule" lockItem="true" />
+                <add name="AnonymousAuthenticationModule" lockItem="true" />
+                <add name="CertificateMappingAuthenticationModule" lockItem="true" />
+                <add name="UrlAuthorizationModule" lockItem="true" />
+                <add name="WindowsAuthenticationModule" lockItem="true" />
+                <add name="IISCertificateMappingAuthenticationModule" lockItem="true" />
+                <add name="WebMatrixSupportModule" lockItem="true" />
+                <add name="IpRestrictionModule" lockItem="true" />
+                <add name="DynamicIpRestrictionModule" lockItem="true" />
+                <add name="RequestFilteringModule" lockItem="true" />
+                <add name="CustomLoggingModule" lockItem="true" />
+                <add name="CustomErrorModule" lockItem="true" />
+                <add name="FailedRequestsTracingModule" lockItem="true" />
+                <add name="CgiModule" lockItem="true" />
+                <add name="FastCgiModule" lockItem="true" />
+<!--                <add name="WebDAVModule" /> -->
+                <add name="RewriteModule" /> 
+                <add name="OutputCache" type="System.Web.Caching.OutputCacheModule" preCondition="managedHandler" />
+                <add name="Session" type="System.Web.SessionState.SessionStateModule" preCondition="managedHandler" />
+                <add name="WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule" preCondition="managedHandler" />
+                <add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" preCondition="managedHandler" />
+                <add name="DefaultAuthentication" type="System.Web.Security.DefaultAuthenticationModule" preCondition="managedHandler" />
+                <add name="RoleManager" type="System.Web.Security.RoleManagerModule" preCondition="managedHandler" />
+                <add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" preCondition="managedHandler" />
+                <add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule" preCondition="managedHandler" />
+                <add name="AnonymousIdentification" type="System.Web.Security.AnonymousIdentificationModule" preCondition="managedHandler" />
+                <add name="Profile" type="System.Web.Profile.ProfileModule" preCondition="managedHandler" />
+                <add name="UrlMappingsModule" type="System.Web.UrlMappingsModule" preCondition="managedHandler" />
+                <add name="ApplicationInitializationModule" lockItem="true" />
+                <add name="WebSocketModule" lockItem="true" />
+                <add name="ServiceModel-4.0" type="System.ServiceModel.Activation.ServiceHttpModule,System.ServiceModel.Activation,Version=4.0.0.0,Culture=neutral,PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler,runtimeVersionv4.0" />
+                <add name="ConfigurationValidationModule" lockItem="true" />
+                <add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" preCondition="managedHandler,runtimeVersionv4.0" />
+                <add name="ScriptModule-4.0" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler,runtimeVersionv4.0" />
+                <add name="AspNetCoreModule" lockItem="true" />
+                <add name="AspNetCoreModuleV2" lockItem="true" />
+            </modules>
+            <handlers accessPolicy="Read, Script">
+<!--                <add name="WebDAV" path="*" verb="PROPFIND,PROPPATCH,MKCOL,PUT,COPY,DELETE,MOVE,LOCK,UNLOCK" modules="WebDAVModule" resourceType="Unspecified" requireAccess="None" /> -->
+                <add name="AXD-ISAPI-4.0_64bit" path="*.axd" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="PageHandlerFactory-ISAPI-4.0_64bit" path="*.aspx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="SimpleHandlerFactory-ISAPI-4.0_64bit" path="*.ashx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="WebServiceHandlerFactory-ISAPI-4.0_64bit" path="*.asmx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="HttpRemotingHandlerFactory-rem-ISAPI-4.0_64bit" path="*.rem" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="HttpRemotingHandlerFactory-soap-ISAPI-4.0_64bit" path="*.soap" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="svc-ISAPI-4.0_64bit" path="*.svc" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" />
+                <add name="rules-ISAPI-4.0_64bit" path="*.rules" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" />
+                <add name="xoml-ISAPI-4.0_64bit" path="*.xoml" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" />
+                <add name="xamlx-ISAPI-4.0_64bit" path="*.xamlx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" />
+                <add name="aspq-ISAPI-4.0_64bit" path="*.aspq" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="cshtm-ISAPI-4.0_64bit" path="*.cshtm" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="cshtml-ISAPI-4.0_64bit" path="*.cshtml" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="vbhtm-ISAPI-4.0_64bit" path="*.vbhtm" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="vbhtml-ISAPI-4.0_64bit" path="*.vbhtml" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="AXD-ISAPI-4.0_32bit" path="*.axd" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="PageHandlerFactory-ISAPI-4.0_32bit" path="*.aspx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="SimpleHandlerFactory-ISAPI-4.0_32bit" path="*.ashx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="WebServiceHandlerFactory-ISAPI-4.0_32bit" path="*.asmx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="HttpRemotingHandlerFactory-rem-ISAPI-4.0_32bit" path="*.rem" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="HttpRemotingHandlerFactory-soap-ISAPI-4.0_32bit" path="*.soap" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="svc-ISAPI-4.0_32bit" path="*.svc" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" />
+                <add name="rules-ISAPI-4.0_32bit" path="*.rules" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" />
+                <add name="xoml-ISAPI-4.0_32bit" path="*.xoml" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" />
+                <add name="xamlx-ISAPI-4.0_32bit" path="*.xamlx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" />
+                <add name="aspq-ISAPI-4.0_32bit" path="*.aspq" verb="*" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="cshtm-ISAPI-4.0_32bit" path="*.cshtm" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="cshtml-ISAPI-4.0_32bit" path="*.cshtml" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="vbhtm-ISAPI-4.0_32bit" path="*.vbhtm" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="vbhtml-ISAPI-4.0_32bit" path="*.vbhtml" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="TraceHandler-Integrated-4.0" path="trace.axd" verb="GET,HEAD,POST,DEBUG" type="System.Web.Handlers.TraceHandler" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="WebAdminHandler-Integrated-4.0" path="WebAdmin.axd" verb="GET,DEBUG" type="System.Web.Handlers.WebAdminHandler" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="AssemblyResourceLoader-Integrated-4.0" path="WebResource.axd" verb="GET,DEBUG" type="System.Web.Handlers.AssemblyResourceLoader" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="PageHandlerFactory-Integrated-4.0" path="*.aspx" verb="GET,HEAD,POST,DEBUG" type="System.Web.UI.PageHandlerFactory" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="SimpleHandlerFactory-Integrated-4.0" path="*.ashx" verb="GET,HEAD,POST,DEBUG" type="System.Web.UI.SimpleHandlerFactory" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="WebServiceHandlerFactory-Integrated-4.0" path="*.asmx" verb="GET,HEAD,POST,DEBUG" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="HttpRemotingHandlerFactory-rem-Integrated-4.0" path="*.rem" verb="GET,HEAD,POST,DEBUG" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="HttpRemotingHandlerFactory-soap-Integrated-4.0" path="*.soap" verb="GET,HEAD,POST,DEBUG" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory, System.Runtime.Remoting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="svc-Integrated-4.0" path="*.svc" verb="*" type="System.ServiceModel.Activation.ServiceHttpHandlerFactory, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="rules-Integrated-4.0" path="*.rules" verb="*" type="System.ServiceModel.Activation.ServiceHttpHandlerFactory, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="xoml-Integrated-4.0" path="*.xoml" verb="*" type="System.ServiceModel.Activation.ServiceHttpHandlerFactory, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="xamlx-Integrated-4.0" path="*.xamlx" verb="GET,HEAD,POST,DEBUG" type="System.Xaml.Hosting.XamlHttpHandlerFactory, System.Xaml.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="aspq-Integrated-4.0" path="*.aspq" verb="GET,HEAD,POST,DEBUG" type="System.Web.HttpForbiddenHandler" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="cshtm-Integrated-4.0" path="*.cshtm" verb="GET,HEAD,POST,DEBUG" type="System.Web.HttpForbiddenHandler" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="cshtml-Integrated-4.0" path="*.cshtml" verb="GET,HEAD,POST,DEBUG" type="System.Web.HttpForbiddenHandler" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="vbhtm-Integrated-4.0" path="*.vbhtm" verb="GET,HEAD,POST,DEBUG" type="System.Web.HttpForbiddenHandler" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="vbhtml-Integrated-4.0" path="*.vbhtml" verb="GET,HEAD,POST,DEBUG" type="System.Web.HttpForbiddenHandler" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="ScriptHandlerFactoryAppServices-Integrated-4.0" path="*_AppService.axd" verb="*" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="ScriptResourceIntegrated-4.0" path="*ScriptResource.axd" verb="GET,HEAD" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" preCondition="integratedMode,runtimeVersionv4.0" />
+                <add name="ASPClassic" path="*.asp" verb="GET,HEAD,POST" modules="IsapiModule" scriptProcessor="%IIS_BIN%\asp.dll" resourceType="File" />
+                <add name="SecurityCertificate" path="*.cer" verb="GET,HEAD,POST" modules="IsapiModule" scriptProcessor="%IIS_BIN%\asp.dll" resourceType="File" />
+                <add name="ISAPI-dll" path="*.dll" verb="*" modules="IsapiModule" resourceType="File" requireAccess="Execute" allowPathInfo="true" />
+                <add name="TraceHandler-Integrated" path="trace.axd" verb="GET,HEAD,POST,DEBUG" type="System.Web.Handlers.TraceHandler" preCondition="integratedMode,runtimeVersionv2.0" />
+                <add name="WebAdminHandler-Integrated" path="WebAdmin.axd" verb="GET,DEBUG" type="System.Web.Handlers.WebAdminHandler" preCondition="integratedMode,runtimeVersionv2.0" />
+                <add name="AssemblyResourceLoader-Integrated" path="WebResource.axd" verb="GET,DEBUG" type="System.Web.Handlers.AssemblyResourceLoader" preCondition="integratedMode,runtimeVersionv2.0" />
+                <add name="PageHandlerFactory-Integrated" path="*.aspx" verb="GET,HEAD,POST,DEBUG" type="System.Web.UI.PageHandlerFactory" preCondition="integratedMode,runtimeVersionv2.0" />
+                <add name="SimpleHandlerFactory-Integrated" path="*.ashx" verb="GET,HEAD,POST,DEBUG" type="System.Web.UI.SimpleHandlerFactory" preCondition="integratedMode,runtimeVersionv2.0" />
+                <add name="WebServiceHandlerFactory-Integrated" path="*.asmx" verb="GET,HEAD,POST,DEBUG" type="System.Web.Services.Protocols.WebServiceHandlerFactory,System.Web.Services,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b03f5f7f11d50a3a" preCondition="integratedMode,runtimeVersionv2.0" />
+                <add name="HttpRemotingHandlerFactory-rem-Integrated" path="*.rem" verb="GET,HEAD,POST,DEBUG" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory,System.Runtime.Remoting,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089" preCondition="integratedMode,runtimeVersionv2.0" />
+                <add name="HttpRemotingHandlerFactory-soap-Integrated" path="*.soap" verb="GET,HEAD,POST,DEBUG" type="System.Runtime.Remoting.Channels.Http.HttpRemotingHandlerFactory,System.Runtime.Remoting,Version=2.0.0.0,Culture=neutral,PublicKeyToken=b77a5c561934e089" preCondition="integratedMode,runtimeVersionv2.0" />
+                <add name="AXD-ISAPI-2.0" path="*.axd" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" responseBufferLimit="0" />
+                <add name="PageHandlerFactory-ISAPI-2.0" path="*.aspx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" responseBufferLimit="0" />
+                <add name="SimpleHandlerFactory-ISAPI-2.0" path="*.ashx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" responseBufferLimit="0" />
+                <add name="WebServiceHandlerFactory-ISAPI-2.0" path="*.asmx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" responseBufferLimit="0" />
+                <add name="HttpRemotingHandlerFactory-rem-ISAPI-2.0" path="*.rem" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" responseBufferLimit="0" />
+                <add name="HttpRemotingHandlerFactory-soap-ISAPI-2.0" path="*.soap" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness32" responseBufferLimit="0" />
+                <add name="AXD-ISAPI-2.0-64" path="*.axd" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" responseBufferLimit="0" />
+                <add name="PageHandlerFactory-ISAPI-2.0-64" path="*.aspx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" responseBufferLimit="0" />
+                <add name="SimpleHandlerFactory-ISAPI-2.0-64" path="*.ashx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" responseBufferLimit="0" />
+                <add name="WebServiceHandlerFactory-ISAPI-2.0-64" path="*.asmx" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" responseBufferLimit="0" />
+                <add name="HttpRemotingHandlerFactory-rem-ISAPI-2.0-64" path="*.rem" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" responseBufferLimit="0" />
+                <add name="HttpRemotingHandlerFactory-soap-ISAPI-2.0-64" path="*.soap" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v2.0.50727\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv2.0,bitness64" responseBufferLimit="0" />
+                <add name="CGI-exe" path="*.exe" verb="*" modules="CgiModule" resourceType="File" requireAccess="Execute" allowPathInfo="true" />
+                <add name="SSINC-stm" path="*.stm" verb="GET,HEAD,POST" modules="ServerSideIncludeModule" resourceType="File" />
+                <add name="SSINC-shtm" path="*.shtm" verb="GET,HEAD,POST" modules="ServerSideIncludeModule" resourceType="File" />
+                <add name="SSINC-shtml" path="*.shtml" verb="GET,HEAD,POST" modules="ServerSideIncludeModule" resourceType="File" />
+                <add name="TRACEVerbHandler" path="*" verb="TRACE" modules="ProtocolSupportModule" requireAccess="None" />
+                <add name="OPTIONSVerbHandler" path="*" verb="OPTIONS" modules="ProtocolSupportModule" requireAccess="None" />
+                <add name="ExtensionlessUrlHandler-ISAPI-4.0_32bit" path="*." verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness32" responseBufferLimit="0" />
+                <add name="ExtensionlessUrlHandler-ISAPI-4.0_64bit" path="*." verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="%windir%\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" preCondition="classicMode,runtimeVersionv4.0,bitness64" responseBufferLimit="0" />
+                <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="GET,HEAD,POST,DEBUG" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" responseBufferLimit="0" />
+                <add name="StaticFile" path="*" verb="*" modules="StaticFileModule,DefaultDocumentModule,DirectoryListingModule" resourceType="Either" requireAccess="Read" />
+            </handlers>
+        </system.webServer>
+    </location>
+</configuration>

BIN
.vs/bitforum/copilot-chat/d1c88368/sessions/f858f589-f449-436d-8558-9227c939a730


BIN
.vs/bitforum/v17/.wsuo


+ 14 - 27
.vs/bitforum/v17/DocumentLayout.backup.json

@@ -3,12 +3,8 @@
   "WorkspaceRootPath": "E:\\workspace\\bitforum\\",
   "Documents": [
     {
-      "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|E:\\workspace\\bitforum\\frontend\\app\\layout.tsx||{0F2454B1-A556-402D-A7D0-1FDE7F99DEE0}",
-      "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:frontend\\app\\layout.tsx||{0F2454B1-A556-402D-A7D0-1FDE7F99DEE0}"
-    },
-    {
-      "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|E:\\workspace\\bitforum\\frontend\\app\\page.tsx||{0F2454B1-A556-402D-A7D0-1FDE7F99DEE0}",
-      "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:frontend\\app\\page.tsx||{0F2454B1-A556-402D-A7D0-1FDE7F99DEE0}"
+      "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|E:\\workspace\\bitforum\\backend\\Controllers\\HomeController.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+      "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:backend\\Controllers\\HomeController.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
     }
   ],
   "DocumentGroupContainers": [
@@ -18,36 +14,27 @@
       "DocumentGroups": [
         {
           "DockedWidth": 200,
-          "SelectedChildIndex": 1,
+          "SelectedChildIndex": 2,
           "Children": [
             {
               "$type": "Bookmark",
               "Name": "ST:0:0:{d78612c7-9962-4b83-95d9-268046dad23a}"
             },
             {
-              "$type": "Document",
-              "DocumentIndex": 0,
-              "Title": "layout.tsx",
-              "DocumentMoniker": "E:\\workspace\\bitforum\\frontend\\app\\layout.tsx",
-              "RelativeDocumentMoniker": "frontend\\app\\layout.tsx",
-              "ToolTip": "E:\\workspace\\bitforum\\frontend\\app\\layout.tsx",
-              "RelativeToolTip": "frontend\\app\\layout.tsx",
-              "ViewState": "AgIAAAAAAAAAAAAAAAAAAA4AAAAjAAAAAAAAAA==",
-              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003213|",
-              "WhenOpened": "2024-12-27T13:50:32.124Z",
-              "EditorCaption": ""
+              "$type": "Bookmark",
+              "Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}"
             },
             {
               "$type": "Document",
-              "DocumentIndex": 1,
-              "Title": "page.tsx",
-              "DocumentMoniker": "E:\\workspace\\bitforum\\frontend\\app\\page.tsx",
-              "RelativeDocumentMoniker": "frontend\\app\\page.tsx",
-              "ToolTip": "E:\\workspace\\bitforum\\frontend\\app\\page.tsx",
-              "RelativeToolTip": "frontend\\app\\page.tsx",
-              "ViewState": "AgIAAAAAAAAAAAAAAAAAABMAAAAfAAAAAAAAAA==",
-              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003213|",
-              "WhenOpened": "2024-12-27T13:50:16.557Z",
+              "DocumentIndex": 0,
+              "Title": "HomeController.cs",
+              "DocumentMoniker": "E:\\workspace\\bitforum\\backend\\Controllers\\HomeController.cs",
+              "RelativeDocumentMoniker": "backend\\Controllers\\HomeController.cs",
+              "ToolTip": "E:\\workspace\\bitforum\\backend\\Controllers\\HomeController.cs",
+              "RelativeToolTip": "backend\\Controllers\\HomeController.cs",
+              "ViewState": "AgIAAAAAAAAAAAAAAADwvwYAAAAsAAAAAAAAAA==",
+              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+              "WhenOpened": "2025-01-07T20:04:30.922Z",
               "EditorCaption": ""
             }
           ]

+ 12 - 25
.vs/bitforum/v17/DocumentLayout.json

@@ -3,12 +3,8 @@
   "WorkspaceRootPath": "E:\\workspace\\bitforum\\",
   "Documents": [
     {
-      "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|E:\\workspace\\bitforum\\frontend\\app\\page.tsx||{0F2454B1-A556-402D-A7D0-1FDE7F99DEE0}",
-      "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:frontend\\app\\page.tsx||{0F2454B1-A556-402D-A7D0-1FDE7F99DEE0}"
-    },
-    {
-      "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|E:\\workspace\\bitforum\\frontend\\app\\layout.tsx||{0F2454B1-A556-402D-A7D0-1FDE7F99DEE0}",
-      "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:frontend\\app\\layout.tsx||{0F2454B1-A556-402D-A7D0-1FDE7F99DEE0}"
+      "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|E:\\workspace\\bitforum\\backend\\Controllers\\HomeController.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+      "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:backend\\Controllers\\HomeController.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
     }
   ],
   "DocumentGroupContainers": [
@@ -25,29 +21,20 @@
               "Name": "ST:0:0:{d78612c7-9962-4b83-95d9-268046dad23a}"
             },
             {
-              "$type": "Document",
-              "DocumentIndex": 1,
-              "Title": "layout.tsx",
-              "DocumentMoniker": "E:\\workspace\\bitforum\\frontend\\app\\layout.tsx",
-              "RelativeDocumentMoniker": "frontend\\app\\layout.tsx",
-              "ToolTip": "E:\\workspace\\bitforum\\frontend\\app\\layout.tsx",
-              "RelativeToolTip": "frontend\\app\\layout.tsx",
-              "ViewState": "AgIAAAAAAAAAAAAAAAAAAA0AAAAAAAAAAAAAAA==",
-              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003213|",
-              "WhenOpened": "2024-12-27T13:50:32.124Z",
-              "EditorCaption": ""
+              "$type": "Bookmark",
+              "Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}"
             },
             {
               "$type": "Document",
               "DocumentIndex": 0,
-              "Title": "page.tsx",
-              "DocumentMoniker": "E:\\workspace\\bitforum\\frontend\\app\\page.tsx",
-              "RelativeDocumentMoniker": "frontend\\app\\page.tsx",
-              "ToolTip": "E:\\workspace\\bitforum\\frontend\\app\\page.tsx",
-              "RelativeToolTip": "frontend\\app\\page.tsx",
-              "ViewState": "AgIAAAAAAAAAAAAAAAAAABAAAAAzAAAAAAAAAA==",
-              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003213|",
-              "WhenOpened": "2024-12-27T13:50:16.557Z",
+              "Title": "HomeController.cs",
+              "DocumentMoniker": "E:\\workspace\\bitforum\\backend\\Controllers\\HomeController.cs",
+              "RelativeDocumentMoniker": "backend\\Controllers\\HomeController.cs",
+              "ToolTip": "E:\\workspace\\bitforum\\backend\\Controllers\\HomeController.cs",
+              "RelativeToolTip": "backend\\Controllers\\HomeController.cs",
+              "ViewState": "AgIAAAAAAAAAAAAAAAAAAAwAAAAdAAAAAAAAAA==",
+              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+              "WhenOpened": "2025-01-07T20:04:30.922Z",
               "EditorCaption": ""
             }
           ]

BIN
.vs/slnx.sqlite


+ 22 - 0
.vscode/launch.json

@@ -0,0 +1,22 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "type": "node",
+            "request": "launch",
+            "name": "Launch Program",
+            "skipFiles": [
+                "<node_internals>/**"
+            ],
+            "program": "${file}",
+            "runtimeExecutable": "npm",
+            "runtimeArgs": [
+                "run", "dev"
+            ],
+            "console": "integratedTerminal"
+        }
+    ]
+}

+ 12 - 0
backend/.gitignore

@@ -0,0 +1,12 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/versions
+.vs/*

BIN
backend/.vs/ProjectEvaluation/bitforum.metadata.v9.bin


BIN
backend/.vs/ProjectEvaluation/bitforum.projects.v9.bin


BIN
backend/.vs/ProjectEvaluation/bitforum.strings.v9.bin


BIN
backend/.vs/bitforum/CopilotIndices/17.12.38.29086/CodeChunks.db


BIN
backend/.vs/bitforum/CopilotIndices/17.12.38.29086/SemanticSymbols.db


BIN
backend/.vs/bitforum/CopilotIndices/17.12.38.29086/SemanticSymbols.db-shm


BIN
backend/.vs/bitforum/CopilotIndices/17.12.38.29086/SemanticSymbols.db-wal


BIN
backend/.vs/bitforum/DesignTimeBuild/.dtbcache.v2


BIN
backend/.vs/bitforum/FileContentIndex/459dda02-cceb-47fb-a347-df62975c9ad4.vsidx


BIN
backend/.vs/bitforum/FileContentIndex/9957048d-4583-4710-a413-10deea870b74.vsidx


BIN
backend/.vs/bitforum/v17/.futdcache.v2


BIN
backend/.vs/bitforum/v17/.suo


+ 55 - 3
backend/.vs/bitforum/v17/DocumentLayout.backup.json

@@ -1,7 +1,20 @@
 {
   "Version": 1,
-  "WorkspaceRootPath": "E:\\workspace\\bitforum\\backend\\bitforum\\",
-  "Documents": [],
+  "WorkspaceRootPath": "E:\\workspace\\bitforum\\backend\\",
+  "Documents": [
+    {
+      "AbsoluteMoniker": "D:0:0:{07B22B2C-9DC6-4B8E-AA16-8F826E758BDD}|bitforum.csproj|e:\\workspace\\bitforum\\backend\\views\\setting\\basic.cshtml||{40D31677-CBC0-4297-A9EF-89D907823A98}",
+      "RelativeMoniker": "D:0:0:{07B22B2C-9DC6-4B8E-AA16-8F826E758BDD}|bitforum.csproj|solutionrelative:views\\setting\\basic.cshtml||{40D31677-CBC0-4297-A9EF-89D907823A98}"
+    },
+    {
+      "AbsoluteMoniker": "D:0:0:{07B22B2C-9DC6-4B8E-AA16-8F826E758BDD}|bitforum.csproj|e:\\workspace\\bitforum\\backend\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+      "RelativeMoniker": "D:0:0:{07B22B2C-9DC6-4B8E-AA16-8F826E758BDD}|bitforum.csproj|solutionrelative:program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+    },
+    {
+      "AbsoluteMoniker": "D:0:0:{07B22B2C-9DC6-4B8E-AA16-8F826E758BDD}|bitforum.csproj|e:\\workspace\\bitforum\\backend\\database\\usercontext.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+      "RelativeMoniker": "D:0:0:{07B22B2C-9DC6-4B8E-AA16-8F826E758BDD}|bitforum.csproj|solutionrelative:database\\usercontext.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+    }
+  ],
   "DocumentGroupContainers": [
     {
       "Orientation": 0,
@@ -9,11 +22,50 @@
       "DocumentGroups": [
         {
           "DockedWidth": 200,
-          "SelectedChildIndex": -1,
+          "SelectedChildIndex": 1,
           "Children": [
             {
               "$type": "Bookmark",
               "Name": "ST:0:0:{d78612c7-9962-4b83-95d9-268046dad23a}"
+            },
+            {
+              "$type": "Document",
+              "DocumentIndex": 0,
+              "Title": "Basic.cshtml",
+              "DocumentMoniker": "E:\\workspace\\bitforum\\backend\\Views\\Setting\\Basic.cshtml",
+              "RelativeDocumentMoniker": "Views\\Setting\\Basic.cshtml",
+              "ToolTip": "E:\\workspace\\bitforum\\backend\\Views\\Setting\\Basic.cshtml",
+              "RelativeToolTip": "Views\\Setting\\Basic.cshtml",
+              "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
+              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000759|",
+              "WhenOpened": "2025-01-10T21:42:51.047Z",
+              "EditorCaption": ""
+            },
+            {
+              "$type": "Document",
+              "DocumentIndex": 1,
+              "Title": "Program.cs",
+              "DocumentMoniker": "E:\\workspace\\bitforum\\backend\\Program.cs",
+              "RelativeDocumentMoniker": "Program.cs",
+              "ToolTip": "E:\\workspace\\bitforum\\backend\\Program.cs",
+              "RelativeToolTip": "Program.cs",
+              "ViewState": "AgIAAAAAAAAAAAAAAAAAAAAAAAAxAAAAAAAAAA==",
+              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+              "WhenOpened": "2025-01-10T21:37:25.789Z",
+              "EditorCaption": ""
+            },
+            {
+              "$type": "Document",
+              "DocumentIndex": 2,
+              "Title": "UserContext.cs",
+              "DocumentMoniker": "E:\\workspace\\bitforum\\backend\\Database\\UserContext.cs",
+              "RelativeDocumentMoniker": "Database\\UserContext.cs",
+              "ToolTip": "E:\\workspace\\bitforum\\backend\\Database\\UserContext.cs",
+              "RelativeToolTip": "Database\\UserContext.cs",
+              "ViewState": "AgIAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAA==",
+              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+              "WhenOpened": "2025-01-10T21:35:29.242Z",
+              "EditorCaption": ""
             }
           ]
         }

+ 38 - 3
backend/.vs/bitforum/v17/DocumentLayout.json

@@ -1,7 +1,16 @@
 {
   "Version": 1,
-  "WorkspaceRootPath": "E:\\workspace\\bitforum\\backend\\bitforum\\",
-  "Documents": [],
+  "WorkspaceRootPath": "E:\\workspace\\bitforum\\backend\\",
+  "Documents": [
+    {
+      "AbsoluteMoniker": "D:0:0:{07B22B2C-9DC6-4B8E-AA16-8F826E758BDD}|bitforum.csproj|e:\\workspace\\bitforum\\backend\\program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+      "RelativeMoniker": "D:0:0:{07B22B2C-9DC6-4B8E-AA16-8F826E758BDD}|bitforum.csproj|solutionrelative:program.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+    },
+    {
+      "AbsoluteMoniker": "D:0:0:{07B22B2C-9DC6-4B8E-AA16-8F826E758BDD}|bitforum.csproj|e:\\workspace\\bitforum\\backend\\database\\usercontext.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
+      "RelativeMoniker": "D:0:0:{07B22B2C-9DC6-4B8E-AA16-8F826E758BDD}|bitforum.csproj|solutionrelative:database\\usercontext.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
+    }
+  ],
   "DocumentGroupContainers": [
     {
       "Orientation": 0,
@@ -9,11 +18,37 @@
       "DocumentGroups": [
         {
           "DockedWidth": 200,
-          "SelectedChildIndex": -1,
+          "SelectedChildIndex": 1,
           "Children": [
             {
               "$type": "Bookmark",
               "Name": "ST:0:0:{d78612c7-9962-4b83-95d9-268046dad23a}"
+            },
+            {
+              "$type": "Document",
+              "DocumentIndex": 0,
+              "Title": "Program.cs",
+              "DocumentMoniker": "E:\\workspace\\bitforum\\backend\\Program.cs",
+              "RelativeDocumentMoniker": "Program.cs",
+              "ToolTip": "E:\\workspace\\bitforum\\backend\\Program.cs",
+              "RelativeToolTip": "Program.cs",
+              "ViewState": "AgIAAAAAAAAAAAAAAAAAABsAAAA8AAAAAAAAAA==",
+              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+              "WhenOpened": "2025-01-10T21:37:25.789Z",
+              "EditorCaption": ""
+            },
+            {
+              "$type": "Document",
+              "DocumentIndex": 1,
+              "Title": "UserContext.cs",
+              "DocumentMoniker": "E:\\workspace\\bitforum\\backend\\Database\\UserContext.cs",
+              "RelativeDocumentMoniker": "Database\\UserContext.cs",
+              "ToolTip": "E:\\workspace\\bitforum\\backend\\Database\\UserContext.cs",
+              "RelativeToolTip": "Database\\UserContext.cs",
+              "ViewState": "AgIAAAAAAAAAAAAAAAAAAAoAAAAAAAAAAAAAAA==",
+              "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
+              "WhenOpened": "2025-01-10T21:35:29.242Z",
+              "EditorCaption": ""
             }
           ]
         }

+ 10 - 0
backend/Areas/Identity/Pages/Account/AccessDenied.cshtml

@@ -0,0 +1,10 @@
+@page
+@model AccessDeniedModel
+@{
+    ViewData["Title"] = "Access denied";
+}
+
+<header>
+    <h1 class="text-danger">@ViewData["Title"]</h1>
+    <p class="text-danger">You do not have access to this resource.</p>
+</header>

+ 23 - 0
backend/Areas/Identity/Pages/Account/AccessDenied.cshtml.cs

@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace bitforum.Areas.Identity.Pages.Account
+{
+    /// <summary>
+    ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+    ///     directly from your code. This API may change or be removed in future releases.
+    /// </summary>
+    public class AccessDeniedModel : PageModel
+    {
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public void OnGet()
+        {
+        }
+    }
+}

+ 8 - 0
backend/Areas/Identity/Pages/Account/ConfirmEmail.cshtml

@@ -0,0 +1,8 @@
+@page
+@model ConfirmEmailModel
+@{
+    ViewData["Title"] = "Confirm email";
+}
+
+<h1>@ViewData["Title"]</h1>
+<partial name="_StatusMessage" model="Model.StatusMessage" />

+ 51 - 0
backend/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs

@@ -0,0 +1,51 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.WebUtilities;
+
+namespace bitforum.Areas.Identity.Pages.Account
+{
+    public class ConfirmEmailModel : PageModel
+    {
+        private readonly UserManager<IdentityUser> _userManager;
+
+        public ConfirmEmailModel(UserManager<IdentityUser> userManager)
+        {
+            _userManager = userManager;
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [TempData]
+        public string StatusMessage { get; set; }
+        public async Task<IActionResult> OnGetAsync(string userId, string code)
+        {
+            if (userId == null || code == null)
+            {
+                return RedirectToPage("/Index");
+            }
+
+            var user = await _userManager.FindByIdAsync(userId);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{userId}'.");
+            }
+
+            code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
+            var result = await _userManager.ConfirmEmailAsync(user, code);
+            StatusMessage = result.Succeeded ? "Thank you for confirming your email." : "Error confirming your email.";
+            return Page();
+        }
+    }
+}

+ 8 - 0
backend/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml

@@ -0,0 +1,8 @@
+@page
+@model ConfirmEmailChangeModel
+@{
+    ViewData["Title"] = "Confirm email change";
+}
+
+<h1>@ViewData["Title"]</h1>
+<partial name="_StatusMessage" model="Model.StatusMessage" />

+ 69 - 0
backend/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml.cs

@@ -0,0 +1,69 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.WebUtilities;
+
+namespace bitforum.Areas.Identity.Pages.Account
+{
+    public class ConfirmEmailChangeModel : PageModel
+    {
+        private readonly UserManager<IdentityUser> _userManager;
+        private readonly SignInManager<IdentityUser> _signInManager;
+
+        public ConfirmEmailChangeModel(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager)
+        {
+            _userManager = userManager;
+            _signInManager = signInManager;
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [TempData]
+        public string StatusMessage { get; set; }
+
+        public async Task<IActionResult> OnGetAsync(string userId, string email, string code)
+        {
+            if (userId == null || email == null || code == null)
+            {
+                return RedirectToPage("/Index");
+            }
+
+            var user = await _userManager.FindByIdAsync(userId);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{userId}'.");
+            }
+
+            code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
+            var result = await _userManager.ChangeEmailAsync(user, email, code);
+            if (!result.Succeeded)
+            {
+                StatusMessage = "Error changing email.";
+                return Page();
+            }
+
+            // In our UI email and user name are one and the same, so when we update the email
+            // we need to update the user name.
+            var setUserNameResult = await _userManager.SetUserNameAsync(user, email);
+            if (!setUserNameResult.Succeeded)
+            {
+                StatusMessage = "Error changing user name.";
+                return Page();
+            }
+
+            await _signInManager.RefreshSignInAsync(user);
+            StatusMessage = "Thank you for confirming your email change.";
+            return Page();
+        }
+    }
+}

+ 33 - 0
backend/Areas/Identity/Pages/Account/ExternalLogin.cshtml

@@ -0,0 +1,33 @@
+@page
+@model ExternalLoginModel
+@{
+    ViewData["Title"] = "Register";
+}
+
+<h1>@ViewData["Title"]</h1>
+<h2 id="external-login-title">Associate your @Model.ProviderDisplayName account.</h2>
+<hr />
+
+<p id="external-login-description" class="text-info">
+    You've successfully authenticated with <strong>@Model.ProviderDisplayName</strong>.
+    Please enter an email address for this site below and click the Register button to finish
+    logging in.
+</p>
+
+<div class="row">
+    <div class="col-md-4">
+        <form asp-page-handler="Confirmation" asp-route-returnUrl="@Model.ReturnUrl" method="post">
+            <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
+            <div class="form-floating mb-3">
+                <input asp-for="Input.Email" class="form-control" autocomplete="email" placeholder="Please enter your email."/>
+                <label asp-for="Input.Email" class="form-label"></label>
+                <span asp-validation-for="Input.Email" class="text-danger"></span>
+            </div>
+            <button type="submit" class="w-100 btn btn-lg btn-primary">Register</button>
+        </form>
+    </div>
+</div>
+
+@section Scripts {
+    <partial name="_ValidationScriptsPartial" />
+}

+ 223 - 0
backend/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs

@@ -0,0 +1,223 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Security.Claims;
+using System.Text;
+using System.Text.Encodings.Web;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.Extensions.Options;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Identity.UI.Services;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.WebUtilities;
+using Microsoft.Extensions.Logging;
+
+namespace bitforum.Areas.Identity.Pages.Account
+{
+    [AllowAnonymous]
+    public class ExternalLoginModel : PageModel
+    {
+        private readonly SignInManager<IdentityUser> _signInManager;
+        private readonly UserManager<IdentityUser> _userManager;
+        private readonly IUserStore<IdentityUser> _userStore;
+        private readonly IUserEmailStore<IdentityUser> _emailStore;
+        private readonly IEmailSender _emailSender;
+        private readonly ILogger<ExternalLoginModel> _logger;
+
+        public ExternalLoginModel(
+            SignInManager<IdentityUser> signInManager,
+            UserManager<IdentityUser> userManager,
+            IUserStore<IdentityUser> userStore,
+            ILogger<ExternalLoginModel> logger,
+            IEmailSender emailSender)
+        {
+            _signInManager = signInManager;
+            _userManager = userManager;
+            _userStore = userStore;
+            _emailStore = GetEmailStore();
+            _logger = logger;
+            _emailSender = emailSender;
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [BindProperty]
+        public InputModel Input { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public string ProviderDisplayName { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public string ReturnUrl { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [TempData]
+        public string ErrorMessage { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public class InputModel
+        {
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [Required]
+            [EmailAddress]
+            public string Email { get; set; }
+        }
+        
+        public IActionResult OnGet() => RedirectToPage("./Login");
+
+        public IActionResult OnPost(string provider, string returnUrl = null)
+        {
+            // Request a redirect to the external login provider.
+            var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl });
+            var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
+            return new ChallengeResult(provider, properties);
+        }
+
+        public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
+        {
+            returnUrl = returnUrl ?? Url.Content("~/");
+            if (remoteError != null)
+            {
+                ErrorMessage = $"Error from external provider: {remoteError}";
+                return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
+            }
+            var info = await _signInManager.GetExternalLoginInfoAsync();
+            if (info == null)
+            {
+                ErrorMessage = "Error loading external login information.";
+                return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
+            }
+
+            // Sign in the user with this external login provider if the user already has a login.
+            var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
+            if (result.Succeeded)
+            {
+                _logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
+                return LocalRedirect(returnUrl);
+            }
+            if (result.IsLockedOut)
+            {
+                return RedirectToPage("./Lockout");
+            }
+            else
+            {
+                // If the user does not have an account, then ask the user to create an account.
+                ReturnUrl = returnUrl;
+                ProviderDisplayName = info.ProviderDisplayName;
+                if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
+                {
+                    Input = new InputModel
+                    {
+                        Email = info.Principal.FindFirstValue(ClaimTypes.Email)
+                    };
+                }
+                return Page();
+            }
+        }
+
+        public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
+        {
+            returnUrl = returnUrl ?? Url.Content("~/");
+            // Get the information about the user from the external login provider
+            var info = await _signInManager.GetExternalLoginInfoAsync();
+            if (info == null)
+            {
+                ErrorMessage = "Error loading external login information during confirmation.";
+                return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
+            }
+
+            if (ModelState.IsValid)
+            {
+                var user = CreateUser();
+
+                await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
+                await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
+
+                var result = await _userManager.CreateAsync(user);
+                if (result.Succeeded)
+                {
+                    result = await _userManager.AddLoginAsync(user, info);
+                    if (result.Succeeded)
+                    {
+                        _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
+
+                        var userId = await _userManager.GetUserIdAsync(user);
+                        var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
+                        code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
+                        var callbackUrl = Url.Page(
+                            "/Account/ConfirmEmail",
+                            pageHandler: null,
+                            values: new { area = "Identity", userId = userId, code = code },
+                            protocol: Request.Scheme);
+
+                        await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
+                            $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
+
+                        // If account confirmation is required, we need to show the link if we don't have a real email sender
+                        if (_userManager.Options.SignIn.RequireConfirmedAccount)
+                        {
+                            return RedirectToPage("./RegisterConfirmation", new { Email = Input.Email });
+                        }
+
+                        await _signInManager.SignInAsync(user, isPersistent: false, info.LoginProvider);
+                        return LocalRedirect(returnUrl);
+                    }
+                }
+                foreach (var error in result.Errors)
+                {
+                    ModelState.AddModelError(string.Empty, error.Description);
+                }
+            }
+
+            ProviderDisplayName = info.ProviderDisplayName;
+            ReturnUrl = returnUrl;
+            return Page();
+        }
+
+        private IdentityUser CreateUser()
+        {
+            try
+            {
+                return Activator.CreateInstance<IdentityUser>();
+            }
+            catch
+            {
+                throw new InvalidOperationException($"Can't create an instance of '{nameof(IdentityUser)}'. " +
+                    $"Ensure that '{nameof(IdentityUser)}' is not an abstract class and has a parameterless constructor, or alternatively " +
+                    $"override the external login page in /Areas/Identity/Pages/Account/ExternalLogin.cshtml");
+            }
+        }
+
+        private IUserEmailStore<IdentityUser> GetEmailStore()
+        {
+            if (!_userManager.SupportsUserEmail)
+            {
+                throw new NotSupportedException("The default UI requires a user store with email support.");
+            }
+            return (IUserEmailStore<IdentityUser>)_userStore;
+        }
+    }
+}

+ 41 - 0
backend/Areas/Identity/Pages/Account/ForgotPassword.cshtml

@@ -0,0 +1,41 @@
+@page
+@model ForgotPasswordModel
+@{
+    ViewData["Title"] = "비밀번호를 잊으셨나요?";
+}
+
+<div id="forgotPasswordForm" class="row row-cols-1 justify-content-center align-items-center min-vh-100">
+    <div class="col col-12 col-sm-7 col-md-5 col-lg-5 col-xl-3">
+        <section class="border rounded p-3">
+            <form method="post" accept-charset="utf-8" autocomplete="off">
+                <small class="d-block pb-2">@ViewData["Title"]</small>
+                <h5>재설정할 이메일을 입력하세요.</h5>
+                <hr />
+                <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
+                <div class="form-floating mb-3">
+                    <input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com" />
+                    <label asp-for="Input.Email" class="form-label"></label>
+                    <span asp-validation-for="Input.Email" class="text-danger"></span>
+                </div>
+                <button type="submit" class="w-100 btn btn-primary">비밀번호 재설정</button>
+                <p class="pt-3 text-center">
+                    <a asp-page="./Login">< 취소하기</a>
+                </p>
+            </form>
+        </section>
+    </div>
+    <div class="col">
+        <div class="text-center ps-3 pe-3">
+            <hr />
+            <small>ⓒ PLAYR. All Rights Reserved</small>
+        </div>
+    </div>
+</div>
+
+@section Scripts {
+    <partial name="_ValidationScriptsPartial" />
+}
+
+@section Styles {
+    <link rel="stylesheet" href="~/css/account.css" asp-append-version="true" />
+}

+ 84 - 0
backend/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs

@@ -0,0 +1,84 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Text;
+using System.Text.Encodings.Web;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Identity.UI.Services;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.WebUtilities;
+
+namespace bitforum.Areas.Identity.Pages.Account
+{
+    public class ForgotPasswordModel : PageModel
+    {
+        private readonly UserManager<IdentityUser> _userManager;
+        private readonly IEmailSender _emailSender;
+
+        public ForgotPasswordModel(UserManager<IdentityUser> userManager, IEmailSender emailSender)
+        {
+            _userManager = userManager;
+            _emailSender = emailSender;
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [BindProperty]
+        public InputModel Input { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public class InputModel
+        {
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [Required]
+            [EmailAddress]
+            public string Email { get; set; }
+        }
+
+        public async Task<IActionResult> OnPostAsync()
+        {
+            if (ModelState.IsValid)
+            {
+                var user = await _userManager.FindByEmailAsync(Input.Email);
+                if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
+                {
+                    // Don't reveal that the user does not exist or is not confirmed
+                    return RedirectToPage("./ForgotPasswordConfirmation");
+                }
+
+                // For more information on how to enable account confirmation and password reset please
+                // visit https://go.microsoft.com/fwlink/?LinkID=532713
+                var code = await _userManager.GeneratePasswordResetTokenAsync(user);
+                code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
+                var callbackUrl = Url.Page(
+                    "/Account/ResetPassword",
+                    pageHandler: null,
+                    values: new { area = "Identity", code },
+                    protocol: Request.Scheme);
+
+                await _emailSender.SendEmailAsync(
+                    Input.Email,
+                    "Reset Password",
+                    $"Please reset your password by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
+
+                return RedirectToPage("./ForgotPasswordConfirmation");
+            }
+
+            return Page();
+        }
+    }
+}

+ 17 - 0
backend/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml

@@ -0,0 +1,17 @@
+@page
+@model ForgotPasswordConfirmation
+@{
+    ViewData["Title"] = "Forgot password confirmation";
+}
+
+@*
+<h1>@ViewData["Title"]</h1>
+<p>
+    Please check your email to reset your password.
+</p>
+*@
+
+<script>
+    alert("받은 메일함을 확인하고 비밀번호를 변경하세요.");
+    window.location.replace("/Identity/Account/Login");
+</script>

+ 25 - 0
backend/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml.cs

@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace bitforum.Areas.Identity.Pages.Account
+{
+    /// <summary>
+    ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+    ///     directly from your code. This API may change or be removed in future releases.
+    /// </summary>
+    [AllowAnonymous]
+    public class ForgotPasswordConfirmation : PageModel
+    {
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public void OnGet()
+        {
+        }
+    }
+}

+ 10 - 0
backend/Areas/Identity/Pages/Account/Lockout.cshtml

@@ -0,0 +1,10 @@
+@page
+@model LockoutModel
+@{
+    ViewData["Title"] = "Locked out";
+}
+
+<header>
+    <h1 class="text-danger">@ViewData["Title"]</h1>
+    <p class="text-danger">This account has been locked out, please try again later.</p>
+</header>

+ 25 - 0
backend/Areas/Identity/Pages/Account/Lockout.cshtml.cs

@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace bitforum.Areas.Identity.Pages.Account
+{
+    /// <summary>
+    ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+    ///     directly from your code. This API may change or be removed in future releases.
+    /// </summary>
+    [AllowAnonymous]
+    public class LockoutModel : PageModel
+    {
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public void OnGet()
+        {
+        }
+    }
+}

+ 95 - 0
backend/Areas/Identity/Pages/Account/Login.cshtml

@@ -0,0 +1,95 @@
+@page
+@model LoginModel
+@{
+    ViewData["Title"] = "로그인";
+}
+
+<div id="loginForm" class="row row-cols-1 justify-content-center align-items-center min-vh-100">
+    <div class="col col-12 col-sm-7 col-md-5 col-lg-5 col-xl-3">
+        <section class="border rounded p-3">
+            <h4>@ViewData["Title"]</h4>
+
+            <form id="account" method="post" accept-charset="utf-8" autocomplete="off">
+                <small>승인된 관계자만 접속이 가능합니다.</small>
+                <hr />
+                <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
+                <div class="form-floating mb-3">
+                    <input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com" />
+                    <label asp-for="Input.Email" class="form-label">Email</label>
+                    <span asp-validation-for="Input.Email" class="text-danger"></span>
+                </div>
+                <div class="form-floating mb-3">
+                    <input asp-for="Input.Password" class="form-control" autocomplete="current-password" aria-required="true" placeholder="password" />
+                    <label asp-for="Input.Password" class="form-label">Password</label>
+                    <span asp-validation-for="Input.Password" class="text-danger"></span>
+                </div>
+                <div class="checkbox mb-2">
+                    <label asp-for="Input.RememberMe" class="form-label">
+                        <input class="form-check-input" asp-for="Input.RememberMe" />
+                        @Html.DisplayNameFor(m => m.Input.RememberMe)
+                    </label>
+                </div>
+                <div class="mb-3">
+                    <button id="login-submit" type="submit" class="w-100 btn btn-primary">로그인</button>
+                </div>
+                <div>
+                    <p>
+                        <a id="forgot-password" asp-page="./ForgotPassword">비밀번호를 잊으셨나요?</a>
+                    </p>
+                    <p>
+                        <a asp-page="./Register" asp-route-returnUrl="@Model.ReturnUrl">회원가입</a>
+                        
+                    </p>
+                    <p class="mb-1">
+                        <a id="resend-confirmation" asp-page="./ResendEmailConfirmation">이메일 재인증</a>
+                    </p>
+                </div>
+            </form>
+        </section>
+    </div>
+    <div class="col">
+        <div class="text-center ps-3 pe-3">
+            <hr />
+            <small>ⓒ PLAYR. All Rights Reserved</small>
+        </div>
+
+        <section class="text-center">
+            @*
+            <h3>Use another service to log in.</h3>
+            <hr />
+            @{
+                if ((Model.ExternalLogins?.Count ?? 0) == 0)
+                {
+                    <div>
+                        <p>
+                            There are no external authentication services configured. See this <a href="https://go.microsoft.com/fwlink/?LinkID=532715">article
+                            about setting up this ASP.NET application to support logging in via external services</a>.
+                        </p>
+                    </div>
+                }
+                else
+                {
+                    <form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
+                        <div>
+                            <p>
+                                @foreach (var provider in Model.ExternalLogins!)
+                                {
+                                    <button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
+                                }
+                            </p>
+                        </div>
+                    </form>
+                }
+            }
+            *@
+        </section>
+    </div>
+</div>
+
+@section Scripts {
+    <partial name="_ValidationScriptsPartial" />
+}
+
+@section Styles {
+    <link rel="stylesheet" href="~/css/account.css" asp-append-version="true" />
+}

+ 141 - 0
backend/Areas/Identity/Pages/Account/Login.cshtml.cs

@@ -0,0 +1,141 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Identity.UI.Services;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+
+namespace bitforum.Areas.Identity.Pages.Account
+{
+    public class LoginModel : PageModel
+    {
+        private readonly SignInManager<IdentityUser> _signInManager;
+        private readonly ILogger<LoginModel> _logger;
+
+        public LoginModel(SignInManager<IdentityUser> signInManager, ILogger<LoginModel> logger)
+        {
+            _signInManager = signInManager;
+            _logger = logger;
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [BindProperty]
+        public InputModel Input { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public IList<AuthenticationScheme> ExternalLogins { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public string ReturnUrl { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [TempData]
+        public string ErrorMessage { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public class InputModel
+        {
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [Required]
+            [EmailAddress]
+            public string Email { get; set; }
+
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [Required]
+            [DataType(DataType.Password)]
+            public string Password { get; set; }
+
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            // [Display(Name = "Remember me?")]
+            [Display(Name = "로그인 상태 유지")]
+            public bool RememberMe { get; set; }
+        }
+
+        public async Task OnGetAsync(string returnUrl = null)
+        {
+            if (!string.IsNullOrEmpty(ErrorMessage))
+            {
+                ModelState.AddModelError(string.Empty, ErrorMessage);
+            }
+
+            returnUrl ??= Url.Content("~/");
+
+            // Clear the existing external cookie to ensure a clean login process
+            await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
+
+            ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
+
+            ReturnUrl = returnUrl;
+        }
+
+        public async Task<IActionResult> OnPostAsync(string returnUrl = null)
+        {
+            returnUrl ??= Url.Content("~/");
+
+            ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
+
+            if (ModelState.IsValid)
+            {
+                // This doesn't count login failures towards account lockout
+                // To enable password failures to trigger account lockout, set lockoutOnFailure: true
+                var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
+                if (result.Succeeded)
+                {
+                    _logger.LogInformation("User logged in.");
+                    return LocalRedirect(returnUrl);
+                }
+                if (result.RequiresTwoFactor)
+                {
+                    return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
+                }
+                if (result.IsLockedOut)
+                {
+                    _logger.LogWarning("User account locked out.");
+                    return RedirectToPage("./Lockout");
+                }
+                else
+                {
+                    ModelState.AddModelError(string.Empty, "Invalid login attempt.");
+                    return Page();
+                }
+            }
+
+            // If we got this far, something failed, redisplay form
+            return Page();
+        }
+    }
+}

+ 39 - 0
backend/Areas/Identity/Pages/Account/LoginWith2fa.cshtml

@@ -0,0 +1,39 @@
+@page
+@model LoginWith2faModel
+@{
+    ViewData["Title"] = "Two-factor authentication";
+}
+
+<h1>@ViewData["Title"]</h1>
+<hr />
+<p>Your login is protected with an authenticator app. Enter your authenticator code below.</p>
+<div class="row">
+    <div class="col-md-4">
+        <form method="post" asp-route-returnUrl="@Model.ReturnUrl">
+            <input asp-for="RememberMe" type="hidden" />
+            <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
+            <div class="form-floating mb-3">
+                <input asp-for="Input.TwoFactorCode" class="form-control" autocomplete="off" />
+                <label asp-for="Input.TwoFactorCode" class="form-label"></label>
+                <span asp-validation-for="Input.TwoFactorCode" class="text-danger"></span>
+            </div>
+            <div class="checkbox mb-3">
+                <label asp-for="Input.RememberMachine" class="form-label">
+                    <input asp-for="Input.RememberMachine" />
+                    @Html.DisplayNameFor(m => m.Input.RememberMachine)
+                </label>
+            </div>
+            <div>
+                <button type="submit" class="w-100 btn btn-lg btn-primary">Log in</button>
+            </div>
+        </form>
+    </div>
+</div>
+<p>
+    Don't have access to your authenticator device? You can
+    <a id="recovery-code-login" asp-page="./LoginWithRecoveryCode" asp-route-returnUrl="@Model.ReturnUrl">log in with a recovery code</a>.
+</p>
+
+@section Scripts {
+    <partial name="_ValidationScriptsPartial" />
+}

+ 131 - 0
backend/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs

@@ -0,0 +1,131 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.Extensions.Logging;
+
+namespace bitforum.Areas.Identity.Pages.Account
+{
+    public class LoginWith2faModel : PageModel
+    {
+        private readonly SignInManager<IdentityUser> _signInManager;
+        private readonly UserManager<IdentityUser> _userManager;
+        private readonly ILogger<LoginWith2faModel> _logger;
+
+        public LoginWith2faModel(
+            SignInManager<IdentityUser> signInManager,
+            UserManager<IdentityUser> userManager,
+            ILogger<LoginWith2faModel> logger)
+        {
+            _signInManager = signInManager;
+            _userManager = userManager;
+            _logger = logger;
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [BindProperty]
+        public InputModel Input { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public bool RememberMe { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public string ReturnUrl { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public class InputModel
+        {
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [Required]
+            [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
+            [DataType(DataType.Text)]
+            [Display(Name = "Authenticator code")]
+            public string TwoFactorCode { get; set; }
+
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [Display(Name = "Remember this machine")]
+            public bool RememberMachine { get; set; }
+        }
+
+        public async Task<IActionResult> OnGetAsync(bool rememberMe, string returnUrl = null)
+        {
+            // Ensure the user has gone through the username & password screen first
+            var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
+
+            if (user == null)
+            {
+                throw new InvalidOperationException($"Unable to load two-factor authentication user.");
+            }
+
+            ReturnUrl = returnUrl;
+            RememberMe = rememberMe;
+
+            return Page();
+        }
+
+        public async Task<IActionResult> OnPostAsync(bool rememberMe, string returnUrl = null)
+        {
+            if (!ModelState.IsValid)
+            {
+                return Page();
+            }
+
+            returnUrl = returnUrl ?? Url.Content("~/");
+
+            var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
+            if (user == null)
+            {
+                throw new InvalidOperationException($"Unable to load two-factor authentication user.");
+            }
+
+            var authenticatorCode = Input.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty);
+
+            var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, Input.RememberMachine);
+
+            var userId = await _userManager.GetUserIdAsync(user);
+
+            if (result.Succeeded)
+            {
+                _logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", user.Id);
+                return LocalRedirect(returnUrl);
+            }
+            else if (result.IsLockedOut)
+            {
+                _logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id);
+                return RedirectToPage("./Lockout");
+            }
+            else
+            {
+                _logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", user.Id);
+                ModelState.AddModelError(string.Empty, "Invalid authenticator code.");
+                return Page();
+            }
+        }
+    }
+}

+ 29 - 0
backend/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml

@@ -0,0 +1,29 @@
+@page
+@model LoginWithRecoveryCodeModel
+@{
+    ViewData["Title"] = "Recovery code verification";
+}
+
+<h1>@ViewData["Title"]</h1>
+<hr />
+<p>
+    You have requested to log in with a recovery code. This login will not be remembered until you provide
+    an authenticator app code at log in or disable 2FA and log in again.
+</p>
+<div class="row">
+    <div class="col-md-4">
+        <form method="post">
+            <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
+            <div class="form-floating mb-3">
+                <input asp-for="Input.RecoveryCode" class="form-control" autocomplete="off" placeholder="RecoveryCode" />
+                <label asp-for="Input.RecoveryCode" class="form-label"></label>
+                <span asp-validation-for="Input.RecoveryCode" class="text-danger"></span>
+            </div>
+            <button type="submit" class="w-100 btn btn-lg btn-primary">Log in</button>
+        </form>
+    </div>
+</div>
+
+@section Scripts {
+    <partial name="_ValidationScriptsPartial" />
+}

+ 112 - 0
backend/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml.cs

@@ -0,0 +1,112 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+namespace bitforum.Areas.Identity.Pages.Account
+{
+    public class LoginWithRecoveryCodeModel : PageModel
+    {
+        private readonly SignInManager<IdentityUser> _signInManager;
+        private readonly UserManager<IdentityUser> _userManager;
+        private readonly ILogger<LoginWithRecoveryCodeModel> _logger;
+
+        public LoginWithRecoveryCodeModel(
+            SignInManager<IdentityUser> signInManager,
+            UserManager<IdentityUser> userManager,
+            ILogger<LoginWithRecoveryCodeModel> logger)
+        {
+            _signInManager = signInManager;
+            _userManager = userManager;
+            _logger = logger;
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [BindProperty]
+        public InputModel Input { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public string ReturnUrl { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public class InputModel
+        {
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [BindProperty]
+            [Required]
+            [DataType(DataType.Text)]
+            [Display(Name = "Recovery Code")]
+            public string RecoveryCode { get; set; }
+        }
+
+        public async Task<IActionResult> OnGetAsync(string returnUrl = null)
+        {
+            // Ensure the user has gone through the username & password screen first
+            var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
+            if (user == null)
+            {
+                throw new InvalidOperationException($"Unable to load two-factor authentication user.");
+            }
+
+            ReturnUrl = returnUrl;
+
+            return Page();
+        }
+
+        public async Task<IActionResult> OnPostAsync(string returnUrl = null)
+        {
+            if (!ModelState.IsValid)
+            {
+                return Page();
+            }
+
+            var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
+            if (user == null)
+            {
+                throw new InvalidOperationException($"Unable to load two-factor authentication user.");
+            }
+
+            var recoveryCode = Input.RecoveryCode.Replace(" ", string.Empty);
+
+            var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
+
+            var userId = await _userManager.GetUserIdAsync(user);
+
+            if (result.Succeeded)
+            {
+                _logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", user.Id);
+                return LocalRedirect(returnUrl ?? Url.Content("~/"));
+            }
+            if (result.IsLockedOut)
+            {
+                _logger.LogWarning("User account locked out.");
+                return RedirectToPage("./Lockout");
+            }
+            else
+            {
+                _logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", user.Id);
+                ModelState.AddModelError(string.Empty, "Invalid recovery code entered.");
+                return Page();
+            }
+        }
+    }
+}

+ 21 - 0
backend/Areas/Identity/Pages/Account/Logout.cshtml

@@ -0,0 +1,21 @@
+@page
+@model LogoutModel
+@{
+    ViewData["Title"] = "Log out";
+}
+
+<header>
+    <h1>@ViewData["Title"]</h1>
+    @{
+        if (User.Identity?.IsAuthenticated ?? false)
+        {
+            <form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Page("/", new { area = "" })" method="post">
+                <button type="submit" class="nav-link btn btn-link text-dark">Click here to Logout</button>
+            </form>
+        }
+        else
+        {
+            <p>You have successfully logged out of the application.</p>
+        }
+    }
+</header>

+ 42 - 0
backend/Areas/Identity/Pages/Account/Logout.cshtml.cs

@@ -0,0 +1,42 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+
+namespace bitforum.Areas.Identity.Pages.Account
+{
+    public class LogoutModel : PageModel
+    {
+        private readonly SignInManager<IdentityUser> _signInManager;
+        private readonly ILogger<LogoutModel> _logger;
+
+        public LogoutModel(SignInManager<IdentityUser> signInManager, ILogger<LogoutModel> logger)
+        {
+            _signInManager = signInManager;
+            _logger = logger;
+        }
+
+        public async Task<IActionResult> OnPost(string returnUrl = null)
+        {
+            await _signInManager.SignOutAsync();
+            _logger.LogInformation("User logged out.");
+            if (returnUrl != null)
+            {
+                return LocalRedirect(returnUrl);
+            }
+            else
+            {
+                // This needs to be a redirect so that the browser performs a new
+                // request and the identity for the user gets updated.
+                return RedirectToPage();
+            }
+        }
+    }
+}

+ 35 - 0
backend/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml

@@ -0,0 +1,35 @@
+@page
+@model ChangePasswordModel
+@{
+    ViewData["Title"] = "Change password";
+    ViewData["ActivePage"] = ManageNavPages.ChangePassword;
+}
+
+<partial name="_StatusMessage" for="StatusMessage" />
+<div class="row">
+    <div class="col-md-6">
+        <form id="change-password-form" method="post">
+            <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
+            <div class="form-floating mb-3">
+                <input asp-for="Input.OldPassword" class="form-control" autocomplete="current-password" aria-required="true" placeholder="Please enter your old password." />
+                <label asp-for="Input.OldPassword" class="form-label"></label>
+                <span asp-validation-for="Input.OldPassword" class="text-danger"></span>
+            </div>
+            <div class="form-floating mb-3">
+                <input asp-for="Input.NewPassword" class="form-control" autocomplete="new-password" aria-required="true" placeholder="Please enter your new password." />
+                <label asp-for="Input.NewPassword" class="form-label"></label>
+                <span asp-validation-for="Input.NewPassword" class="text-danger"></span>
+            </div>
+            <div class="form-floating mb-3">
+                <input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" aria-required="true" placeholder="Please confirm your new password."/>
+                <label asp-for="Input.ConfirmPassword" class="form-label"></label>
+                <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
+            </div>
+            <button type="submit" class="w-100 btn btn-primary">변경하기</button>
+        </form>
+    </div>
+</div>
+
+@section Scripts {
+    <partial name="_ValidationScriptsPartial" />
+}

+ 127 - 0
backend/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs

@@ -0,0 +1,127 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+
+namespace bitforum.Areas.Identity.Pages.Account.Manage
+{
+    public class ChangePasswordModel : PageModel
+    {
+        private readonly UserManager<IdentityUser> _userManager;
+        private readonly SignInManager<IdentityUser> _signInManager;
+        private readonly ILogger<ChangePasswordModel> _logger;
+
+        public ChangePasswordModel(
+            UserManager<IdentityUser> userManager,
+            SignInManager<IdentityUser> signInManager,
+            ILogger<ChangePasswordModel> logger)
+        {
+            _userManager = userManager;
+            _signInManager = signInManager;
+            _logger = logger;
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [BindProperty]
+        public InputModel Input { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [TempData]
+        public string StatusMessage { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public class InputModel
+        {
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [Required]
+            [DataType(DataType.Password)]
+            [Display(Name = "Current password")]
+            public string OldPassword { get; set; }
+
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [Required]
+            [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
+            [DataType(DataType.Password)]
+            [Display(Name = "New password")]
+            public string NewPassword { get; set; }
+
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [DataType(DataType.Password)]
+            [Display(Name = "Confirm new password")]
+            [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
+            public string ConfirmPassword { get; set; }
+        }
+
+        public async Task<IActionResult> OnGetAsync()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            var hasPassword = await _userManager.HasPasswordAsync(user);
+            if (!hasPassword)
+            {
+                return RedirectToPage("./SetPassword");
+            }
+
+            return Page();
+        }
+
+        public async Task<IActionResult> OnPostAsync()
+        {
+            if (!ModelState.IsValid)
+            {
+                return Page();
+            }
+
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            var changePasswordResult = await _userManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword);
+            if (!changePasswordResult.Succeeded)
+            {
+                foreach (var error in changePasswordResult.Errors)
+                {
+                    ModelState.AddModelError(string.Empty, error.Description);
+                }
+                return Page();
+            }
+
+            await _signInManager.RefreshSignInAsync(user);
+            _logger.LogInformation("User changed their password successfully.");
+            StatusMessage = "Your password has been changed.";
+
+            return RedirectToPage();
+        }
+    }
+}

+ 33 - 0
backend/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml

@@ -0,0 +1,33 @@
+@page
+@model DeletePersonalDataModel
+@{
+    ViewData["Title"] = "회원탈퇴";
+    ViewData["ActivePage"] = ManageNavPages.PersonalData;
+}
+
+<h3>@ViewData["Title"]</h3>
+
+<div class="alert alert-warning" role="alert">
+    <p class="pb-0 mb-0">
+        <strong>이 데이터를 삭제하면 계정이 영구적으로 삭제되며, 이는 복구할 수 없습니다.</strong>
+    </p>
+</div>
+
+<div>
+    <form id="delete-user" method="post" accept-charset="utf-8" autocomplete="off">
+        <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
+        @if (Model.RequirePassword)
+        {
+            <div class="form-floating mb-3">
+                <input asp-for="Input.Password" class="form-control" autocomplete="current-password" aria-required="true" placeholder="Please enter your password." />
+                <label asp-for="Input.Password" class="form-label"></label>
+                <span asp-validation-for="Input.Password" class="text-danger"></span>
+            </div>
+        }
+        <button class="w-100 btn btn-danger" type="submit">회원탈퇴 처리에 동의합니다.</button>
+    </form>
+</div>
+
+@section Scripts {
+    <partial name="_ValidationScriptsPartial" />
+}

+ 103 - 0
backend/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs

@@ -0,0 +1,103 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+
+namespace bitforum.Areas.Identity.Pages.Account.Manage
+{
+    public class DeletePersonalDataModel : PageModel
+    {
+        private readonly UserManager<IdentityUser> _userManager;
+        private readonly SignInManager<IdentityUser> _signInManager;
+        private readonly ILogger<DeletePersonalDataModel> _logger;
+
+        public DeletePersonalDataModel(
+            UserManager<IdentityUser> userManager,
+            SignInManager<IdentityUser> signInManager,
+            ILogger<DeletePersonalDataModel> logger)
+        {
+            _userManager = userManager;
+            _signInManager = signInManager;
+            _logger = logger;
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [BindProperty]
+        public InputModel Input { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public class InputModel
+        {
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [Required]
+            [DataType(DataType.Password)]
+            public string Password { get; set; }
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public bool RequirePassword { get; set; }
+
+        public async Task<IActionResult> OnGet()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            RequirePassword = await _userManager.HasPasswordAsync(user);
+            return Page();
+        }
+
+        public async Task<IActionResult> OnPostAsync()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            RequirePassword = await _userManager.HasPasswordAsync(user);
+            if (RequirePassword)
+            {
+                if (!await _userManager.CheckPasswordAsync(user, Input.Password))
+                {
+                    ModelState.AddModelError(string.Empty, "Incorrect password.");
+                    return Page();
+                }
+            }
+
+            var result = await _userManager.DeleteAsync(user);
+            var userId = await _userManager.GetUserIdAsync(user);
+            if (!result.Succeeded)
+            {
+                throw new InvalidOperationException($"Unexpected error occurred deleting user.");
+            }
+
+            await _signInManager.SignOutAsync();
+
+            _logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId);
+
+            return Redirect("~/");
+        }
+    }
+}

+ 25 - 0
backend/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml

@@ -0,0 +1,25 @@
+@page
+@model Disable2faModel
+@{
+    ViewData["Title"] = "Disable two-factor authentication (2FA)";
+    ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
+}
+
+<partial name="_StatusMessage" for="StatusMessage" />
+<h3>@ViewData["Title"]</h3>
+
+<div class="alert alert-warning" role="alert">
+    <p>
+        <strong>This action only disables 2FA.</strong>
+    </p>
+    <p>
+        Disabling 2FA does not change the keys used in authenticator apps. If you wish to change the key
+        used in an authenticator app you should <a asp-page="./ResetAuthenticator">reset your authenticator keys.</a>
+    </p>
+</div>
+
+<div>
+    <form method="post">
+        <button class="btn btn-danger" type="submit">Disable 2FA</button>
+    </form>
+</div>

+ 69 - 0
backend/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs

@@ -0,0 +1,69 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+
+namespace bitforum.Areas.Identity.Pages.Account.Manage
+{
+    public class Disable2faModel : PageModel
+    {
+        private readonly UserManager<IdentityUser> _userManager;
+        private readonly ILogger<Disable2faModel> _logger;
+
+        public Disable2faModel(
+            UserManager<IdentityUser> userManager,
+            ILogger<Disable2faModel> logger)
+        {
+            _userManager = userManager;
+            _logger = logger;
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [TempData]
+        public string StatusMessage { get; set; }
+
+        public async Task<IActionResult> OnGet()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            if (!await _userManager.GetTwoFactorEnabledAsync(user))
+            {
+                throw new InvalidOperationException($"Cannot disable 2FA for user as it's not currently enabled.");
+            }
+
+            return Page();
+        }
+
+        public async Task<IActionResult> OnPostAsync()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false);
+            if (!disable2faResult.Succeeded)
+            {
+                throw new InvalidOperationException($"Unexpected error occurred disabling 2FA.");
+            }
+
+            _logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", _userManager.GetUserId(User));
+            StatusMessage = "2fa has been disabled. You can reenable 2fa when you setup an authenticator app";
+            return RedirectToPage("./TwoFactorAuthentication");
+        }
+    }
+}

+ 12 - 0
backend/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml

@@ -0,0 +1,12 @@
+@page
+@model DownloadPersonalDataModel
+@{
+    ViewData["Title"] = "Download Your Data";
+    ViewData["ActivePage"] = ManageNavPages.PersonalData;
+}
+
+<h3>@ViewData["Title"]</h3>
+
+@section Scripts {
+    <partial name="_ValidationScriptsPartial" />
+}

+ 67 - 0
backend/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml.cs

@@ -0,0 +1,67 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+
+namespace bitforum.Areas.Identity.Pages.Account.Manage
+{
+    public class DownloadPersonalDataModel : PageModel
+    {
+        private readonly UserManager<IdentityUser> _userManager;
+        private readonly ILogger<DownloadPersonalDataModel> _logger;
+
+        public DownloadPersonalDataModel(
+            UserManager<IdentityUser> userManager,
+            ILogger<DownloadPersonalDataModel> logger)
+        {
+            _userManager = userManager;
+            _logger = logger;
+        }
+
+        public IActionResult OnGet()
+        {
+            return NotFound();
+        }
+
+        public async Task<IActionResult> OnPostAsync()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            _logger.LogInformation("User with ID '{UserId}' asked for their personal data.", _userManager.GetUserId(User));
+
+            // Only include personal data for download
+            var personalData = new Dictionary<string, string>();
+            var personalDataProps = typeof(IdentityUser).GetProperties().Where(
+                            prop => Attribute.IsDefined(prop, typeof(PersonalDataAttribute)));
+            foreach (var p in personalDataProps)
+            {
+                personalData.Add(p.Name, p.GetValue(user)?.ToString() ?? "null");
+            }
+
+            var logins = await _userManager.GetLoginsAsync(user);
+            foreach (var l in logins)
+            {
+                personalData.Add($"{l.LoginProvider} external login provider key", l.ProviderKey);
+            }
+
+            personalData.Add($"Authenticator Key", await _userManager.GetAuthenticatorKeyAsync(user));
+
+            Response.Headers.TryAdd("Content-Disposition", "attachment; filename=PersonalData.json");
+            return new FileContentResult(JsonSerializer.SerializeToUtf8Bytes(personalData), "application/json");
+        }
+    }
+}

+ 44 - 0
backend/Areas/Identity/Pages/Account/Manage/Email.cshtml

@@ -0,0 +1,44 @@
+@page
+@model EmailModel
+@{
+    ViewData["Title"] = "Manage Email";
+    ViewData["ActivePage"] = ManageNavPages.Email;
+}
+
+<partial name="_StatusMessage" for="StatusMessage" />
+<div class="container">
+    <div class="row">
+        <div class="col-md-6">
+            <form id="email-form" method="post">
+                <div asp-validation-summary="All" class="text-danger" role="alert"></div>
+                @if (Model.IsEmailConfirmed)
+                {
+                    <div class="form-floating mb-3 input-group">
+                        <input asp-for="Email" class="form-control" placeholder="Please enter your email." disabled />
+                            <div class="input-group-append">
+                                <span class="h-100 input-group-text text-success font-weight-bold">✓</span>
+                            </div>
+                        <label asp-for="Email" class="form-label"></label>
+                    </div>
+                }
+                else
+                {
+                    <div class="form-floating mb-3">
+                        <input asp-for="Email" class="form-control" placeholder="Please enter your email." disabled />
+                        <label asp-for="Email" class="form-label"></label>
+                        <button id="email-verification" type="submit" asp-page-handler="SendVerificationEmail" class="btn btn-link">Send verification email</button>
+                    </div>
+                }
+                <div class="form-floating mb-3">
+                    <input asp-for="Input.NewEmail" class="form-control" autocomplete="email" aria-required="true" placeholder="Please enter new email." />
+                    <label asp-for="Input.NewEmail" class="form-label"></label>
+                    <span asp-validation-for="Input.NewEmail" class="text-danger"></span>
+                </div>
+                <button id="change-email-button" type="submit" asp-page-handler="ChangeEmail" class="w-100 btn btn-primary">변경하기</button>
+            </form>
+        </div>
+    </div>
+</div>
+@section Scripts {
+    <partial name="_ValidationScriptsPartial" />
+}

+ 171 - 0
backend/Areas/Identity/Pages/Account/Manage/Email.cshtml.cs

@@ -0,0 +1,171 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Text;
+using System.Text.Encodings.Web;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Identity.UI.Services;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.WebUtilities;
+
+namespace bitforum.Areas.Identity.Pages.Account.Manage
+{
+    public class EmailModel : PageModel
+    {
+        private readonly UserManager<IdentityUser> _userManager;
+        private readonly SignInManager<IdentityUser> _signInManager;
+        private readonly IEmailSender _emailSender;
+
+        public EmailModel(
+            UserManager<IdentityUser> userManager,
+            SignInManager<IdentityUser> signInManager,
+            IEmailSender emailSender)
+        {
+            _userManager = userManager;
+            _signInManager = signInManager;
+            _emailSender = emailSender;
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public string Email { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public bool IsEmailConfirmed { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [TempData]
+        public string StatusMessage { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [BindProperty]
+        public InputModel Input { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public class InputModel
+        {
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [Required]
+            [EmailAddress]
+            [Display(Name = "New email")]
+            public string NewEmail { get; set; }
+        }
+
+        private async Task LoadAsync(IdentityUser user)
+        {
+            var email = await _userManager.GetEmailAsync(user);
+            Email = email;
+
+            Input = new InputModel
+            {
+                NewEmail = email,
+            };
+
+            IsEmailConfirmed = await _userManager.IsEmailConfirmedAsync(user);
+        }
+
+        public async Task<IActionResult> OnGetAsync()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            await LoadAsync(user);
+            return Page();
+        }
+
+        public async Task<IActionResult> OnPostChangeEmailAsync()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            if (!ModelState.IsValid)
+            {
+                await LoadAsync(user);
+                return Page();
+            }
+
+            var email = await _userManager.GetEmailAsync(user);
+            if (Input.NewEmail != email)
+            {
+                var userId = await _userManager.GetUserIdAsync(user);
+                var code = await _userManager.GenerateChangeEmailTokenAsync(user, Input.NewEmail);
+                code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
+                var callbackUrl = Url.Page(
+                    "/Account/ConfirmEmailChange",
+                    pageHandler: null,
+                    values: new { area = "Identity", userId = userId, email = Input.NewEmail, code = code },
+                    protocol: Request.Scheme);
+                await _emailSender.SendEmailAsync(
+                    Input.NewEmail,
+                    "Confirm your email",
+                    $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
+
+                StatusMessage = "Confirmation link to change email sent. Please check your email.";
+                return RedirectToPage();
+            }
+
+            StatusMessage = "Your email is unchanged.";
+            return RedirectToPage();
+        }
+
+        public async Task<IActionResult> OnPostSendVerificationEmailAsync()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            if (!ModelState.IsValid)
+            {
+                await LoadAsync(user);
+                return Page();
+            }
+
+            var userId = await _userManager.GetUserIdAsync(user);
+            var email = await _userManager.GetEmailAsync(user);
+            var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
+            code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
+            var callbackUrl = Url.Page(
+                "/Account/ConfirmEmail",
+                pageHandler: null,
+                values: new { area = "Identity", userId = userId, code = code },
+                protocol: Request.Scheme);
+            await _emailSender.SendEmailAsync(
+                email,
+                "Confirm your email",
+                $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
+
+            StatusMessage = "Verification email sent. Please check your email.";
+            return RedirectToPage();
+        }
+    }
+}

+ 53 - 0
backend/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml

@@ -0,0 +1,53 @@
+@page
+@model EnableAuthenticatorModel
+@{
+    ViewData["Title"] = "Configure authenticator app";
+    ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
+}
+
+<partial name="_StatusMessage" for="StatusMessage" />
+<h3>@ViewData["Title"]</h3>
+<div>
+    <p>To use an authenticator app go through the following steps:</p>
+    <ol class="list">
+        <li>
+            <p>
+                Download a two-factor authenticator app like Microsoft Authenticator for
+                <a href="https://go.microsoft.com/fwlink/?Linkid=825072">Android</a> and
+                <a href="https://go.microsoft.com/fwlink/?Linkid=825073">iOS</a> or
+                Google Authenticator for
+                <a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&amp;hl=en">Android</a> and
+                <a href="https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8">iOS</a>.
+            </p>
+        </li>
+        <li>
+            <p>Scan the QR Code or enter this key <kbd>@Model.SharedKey</kbd> into your two factor authenticator app. Spaces and casing do not matter.</p>
+            <div class="alert alert-info">Learn how to <a href="https://go.microsoft.com/fwlink/?Linkid=852423">enable QR code generation</a>.</div>
+            <div id="qrCode"></div>
+            <div id="qrCodeData" data-url="@Model.AuthenticatorUri"></div>
+        </li>
+        <li>
+            <p>
+                Once you have scanned the QR code or input the key above, your two factor authentication app will provide you
+                with a unique code. Enter the code in the confirmation box below.
+            </p>
+            <div class="row">
+                <div class="col-md-6">
+                    <form id="send-code" method="post">
+                        <div class="form-floating mb-3">
+                            <input asp-for="Input.Code" class="form-control" autocomplete="off" placeholder="Please enter the code."/>
+                            <label asp-for="Input.Code" class="control-label form-label">Verification Code</label>
+                            <span asp-validation-for="Input.Code" class="text-danger"></span>
+                        </div>
+                        <button type="submit" class="w-100 btn btn-lg btn-primary">Verify</button>
+                        <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
+                    </form>
+                </div>
+            </div>
+        </li>
+    </ol>
+</div>
+
+@section Scripts {
+    <partial name="_ValidationScriptsPartial" />
+}

+ 188 - 0
backend/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs

@@ -0,0 +1,188 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Text.Encodings.Web;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+
+namespace bitforum.Areas.Identity.Pages.Account.Manage
+{
+    public class EnableAuthenticatorModel : PageModel
+    {
+        private readonly UserManager<IdentityUser> _userManager;
+        private readonly ILogger<EnableAuthenticatorModel> _logger;
+        private readonly UrlEncoder _urlEncoder;
+
+        private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
+
+        public EnableAuthenticatorModel(
+            UserManager<IdentityUser> userManager,
+            ILogger<EnableAuthenticatorModel> logger,
+            UrlEncoder urlEncoder)
+        {
+            _userManager = userManager;
+            _logger = logger;
+            _urlEncoder = urlEncoder;
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public string SharedKey { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public string AuthenticatorUri { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [TempData]
+        public string[] RecoveryCodes { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [TempData]
+        public string StatusMessage { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [BindProperty]
+        public InputModel Input { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public class InputModel
+        {
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [Required]
+            [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
+            [DataType(DataType.Text)]
+            [Display(Name = "Verification Code")]
+            public string Code { get; set; }
+        }
+
+        public async Task<IActionResult> OnGetAsync()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            await LoadSharedKeyAndQrCodeUriAsync(user);
+
+            return Page();
+        }
+
+        public async Task<IActionResult> OnPostAsync()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            if (!ModelState.IsValid)
+            {
+                await LoadSharedKeyAndQrCodeUriAsync(user);
+                return Page();
+            }
+
+            // Strip spaces and hyphens
+            var verificationCode = Input.Code.Replace(" ", string.Empty).Replace("-", string.Empty);
+
+            var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync(
+                user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);
+
+            if (!is2faTokenValid)
+            {
+                ModelState.AddModelError("Input.Code", "Verification code is invalid.");
+                await LoadSharedKeyAndQrCodeUriAsync(user);
+                return Page();
+            }
+
+            await _userManager.SetTwoFactorEnabledAsync(user, true);
+            var userId = await _userManager.GetUserIdAsync(user);
+            _logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", userId);
+
+            StatusMessage = "Your authenticator app has been verified.";
+
+            if (await _userManager.CountRecoveryCodesAsync(user) == 0)
+            {
+                var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
+                RecoveryCodes = recoveryCodes.ToArray();
+                return RedirectToPage("./ShowRecoveryCodes");
+            }
+            else
+            {
+                return RedirectToPage("./TwoFactorAuthentication");
+            }
+        }
+
+        private async Task LoadSharedKeyAndQrCodeUriAsync(IdentityUser user)
+        {
+            // Load the authenticator key & QR code URI to display on the form
+            var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
+            if (string.IsNullOrEmpty(unformattedKey))
+            {
+                await _userManager.ResetAuthenticatorKeyAsync(user);
+                unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
+            }
+
+            SharedKey = FormatKey(unformattedKey);
+
+            var email = await _userManager.GetEmailAsync(user);
+            AuthenticatorUri = GenerateQrCodeUri(email, unformattedKey);
+        }
+
+        private string FormatKey(string unformattedKey)
+        {
+            var result = new StringBuilder();
+            int currentPosition = 0;
+            while (currentPosition + 4 < unformattedKey.Length)
+            {
+                result.Append(unformattedKey.AsSpan(currentPosition, 4)).Append(' ');
+                currentPosition += 4;
+            }
+            if (currentPosition < unformattedKey.Length)
+            {
+                result.Append(unformattedKey.AsSpan(currentPosition));
+            }
+
+            return result.ToString().ToLowerInvariant();
+        }
+
+        private string GenerateQrCodeUri(string email, string unformattedKey)
+        {
+            return string.Format(
+                CultureInfo.InvariantCulture,
+                AuthenticatorUriFormat,
+                _urlEncoder.Encode("Microsoft.AspNetCore.Identity.UI"),
+                _urlEncoder.Encode(email),
+                unformattedKey);
+        }
+    }
+}

+ 53 - 0
backend/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml

@@ -0,0 +1,53 @@
+@page
+@model ExternalLoginsModel
+@{
+    ViewData["Title"] = "Manage your external logins";
+    ViewData["ActivePage"] = ManageNavPages.ExternalLogins;
+}
+
+<partial name="_StatusMessage" for="StatusMessage" />
+@if (Model.CurrentLogins?.Count > 0)
+{
+    <h3>Registered Logins</h3>
+    <table class="table">
+        <tbody>
+            @foreach (var login in Model.CurrentLogins)
+            {
+                <tr>
+                    <td id="@($"login-provider-{login.LoginProvider}")">@login.ProviderDisplayName</td>
+                    <td>
+                        @if (Model.ShowRemoveButton)
+                        {
+                            <form id="@($"remove-login-{login.LoginProvider}")" asp-page-handler="RemoveLogin" method="post">
+                                <div>
+                                    <input asp-for="@login.LoginProvider" name="LoginProvider" type="hidden" />
+                                    <input asp-for="@login.ProviderKey" name="ProviderKey" type="hidden" />
+                                    <button type="submit" class="btn btn-primary" title="Remove this @login.ProviderDisplayName login from your account">Remove</button>
+                                </div>
+                            </form>
+                        }
+                        else
+                        {
+                            @: &nbsp;
+                        }
+                    </td>
+                </tr>
+            }
+        </tbody>
+    </table>
+}
+@if (Model.OtherLogins?.Count > 0)
+{
+    <h4>Add another service to log in.</h4>
+    <hr />
+    <form id="link-login-form" asp-page-handler="LinkLogin" method="post" class="form-horizontal">
+        <div id="socialLoginList">
+            <p>
+                @foreach (var provider in Model.OtherLogins)
+                {
+                    <button id="@($"link-login-button-{provider.Name}")" type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
+                }
+            </p>
+        </div>
+    </form>
+}

+ 141 - 0
backend/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml.cs

@@ -0,0 +1,141 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace bitforum.Areas.Identity.Pages.Account.Manage
+{
+    public class ExternalLoginsModel : PageModel
+    {
+        private readonly UserManager<IdentityUser> _userManager;
+        private readonly SignInManager<IdentityUser> _signInManager;
+        private readonly IUserStore<IdentityUser> _userStore;
+
+        public ExternalLoginsModel(
+            UserManager<IdentityUser> userManager,
+            SignInManager<IdentityUser> signInManager,
+            IUserStore<IdentityUser> userStore)
+        {
+            _userManager = userManager;
+            _signInManager = signInManager;
+            _userStore = userStore;
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public IList<UserLoginInfo> CurrentLogins { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public IList<AuthenticationScheme> OtherLogins { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public bool ShowRemoveButton { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [TempData]
+        public string StatusMessage { get; set; }
+
+        public async Task<IActionResult> OnGetAsync()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            CurrentLogins = await _userManager.GetLoginsAsync(user);
+            OtherLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
+                .Where(auth => CurrentLogins.All(ul => auth.Name != ul.LoginProvider))
+                .ToList();
+
+            string passwordHash = null;
+            if (_userStore is IUserPasswordStore<IdentityUser> userPasswordStore)
+            {
+                passwordHash = await userPasswordStore.GetPasswordHashAsync(user, HttpContext.RequestAborted);
+            }
+
+            ShowRemoveButton = passwordHash != null || CurrentLogins.Count > 1;
+            return Page();
+        }
+
+        public async Task<IActionResult> OnPostRemoveLoginAsync(string loginProvider, string providerKey)
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            var result = await _userManager.RemoveLoginAsync(user, loginProvider, providerKey);
+            if (!result.Succeeded)
+            {
+                StatusMessage = "The external login was not removed.";
+                return RedirectToPage();
+            }
+
+            await _signInManager.RefreshSignInAsync(user);
+            StatusMessage = "The external login was removed.";
+            return RedirectToPage();
+        }
+
+        public async Task<IActionResult> OnPostLinkLoginAsync(string provider)
+        {
+            // Clear the existing external cookie to ensure a clean login process
+            await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
+
+            // Request a redirect to the external login provider to link a login for the current user
+            var redirectUrl = Url.Page("./ExternalLogins", pageHandler: "LinkLoginCallback");
+            var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User));
+            return new ChallengeResult(provider, properties);
+        }
+
+        public async Task<IActionResult> OnGetLinkLoginCallbackAsync()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            var userId = await _userManager.GetUserIdAsync(user);
+            var info = await _signInManager.GetExternalLoginInfoAsync(userId);
+            if (info == null)
+            {
+                throw new InvalidOperationException($"Unexpected error occurred loading external login info.");
+            }
+
+            var result = await _userManager.AddLoginAsync(user, info);
+            if (!result.Succeeded)
+            {
+                StatusMessage = "The external login was not added. External logins can only be associated with one account.";
+                return RedirectToPage();
+            }
+
+            // Clear the existing external cookie to ensure a clean login process
+            await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
+
+            StatusMessage = "The external login was added.";
+            return RedirectToPage();
+        }
+    }
+}

+ 27 - 0
backend/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml

@@ -0,0 +1,27 @@
+@page
+@model GenerateRecoveryCodesModel
+@{
+    ViewData["Title"] = "Generate two-factor authentication (2FA) recovery codes";
+    ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
+}
+
+<partial name="_StatusMessage" for="StatusMessage" />
+<h3>@ViewData["Title"]</h3>
+<div class="alert alert-warning" role="alert">
+    <p>
+        <span class="glyphicon glyphicon-warning-sign"></span>
+        <strong>Put these codes in a safe place.</strong>
+    </p>
+    <p>
+        If you lose your device and don't have the recovery codes you will lose access to your account.
+    </p>
+    <p>
+        Generating new recovery codes does not change the keys used in authenticator apps. If you wish to change the key
+        used in an authenticator app you should <a asp-page="./ResetAuthenticator">reset your authenticator keys.</a>
+    </p>
+</div>
+<div>
+    <form method="post">
+        <button class="btn btn-danger" type="submit">Generate Recovery Codes</button>
+    </form>
+</div>

+ 82 - 0
backend/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs

@@ -0,0 +1,82 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+
+namespace bitforum.Areas.Identity.Pages.Account.Manage
+{
+    public class GenerateRecoveryCodesModel : PageModel
+    {
+        private readonly UserManager<IdentityUser> _userManager;
+        private readonly ILogger<GenerateRecoveryCodesModel> _logger;
+
+        public GenerateRecoveryCodesModel(
+            UserManager<IdentityUser> userManager,
+            ILogger<GenerateRecoveryCodesModel> logger)
+        {
+            _userManager = userManager;
+            _logger = logger;
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [TempData]
+        public string[] RecoveryCodes { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [TempData]
+        public string StatusMessage { get; set; }
+
+        public async Task<IActionResult> OnGetAsync()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
+            if (!isTwoFactorEnabled)
+            {
+                throw new InvalidOperationException($"Cannot generate recovery codes for user because they do not have 2FA enabled.");
+            }
+
+            return Page();
+        }
+
+        public async Task<IActionResult> OnPostAsync()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
+            var userId = await _userManager.GetUserIdAsync(user);
+            if (!isTwoFactorEnabled)
+            {
+                throw new InvalidOperationException($"Cannot generate recovery codes for user as they do not have 2FA enabled.");
+            }
+
+            var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
+            RecoveryCodes = recoveryCodes.ToArray();
+
+            _logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", userId);
+            StatusMessage = "You have generated new recovery codes.";
+            return RedirectToPage("./ShowRecoveryCodes");
+        }
+    }
+}

+ 32 - 0
backend/Areas/Identity/Pages/Account/Manage/Index.cshtml

@@ -0,0 +1,32 @@
+@page
+@model IndexModel
+@{
+    ViewData["Title"] = "Profile";
+    ViewData["ActivePage"] = ManageNavPages.Index;
+}
+
+<partial name="_StatusMessage" for="StatusMessage" />
+
+<div class="contaasdasdner">
+    <div class="row">
+        <div class="col-md-6">
+            <form id="profile-form" method="post">
+                <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
+                <div class="form-floating mb-3">
+                    <input asp-for="Username" class="form-control" placeholder="Please choose your username." disabled />
+                    <label asp-for="Username" class="form-label"></label>
+                </div>
+                <div class="form-floating mb-3">
+                    <input asp-for="Input.PhoneNumber" class="form-control" placeholder="Please enter your phone number."/>
+                    <label asp-for="Input.PhoneNumber" class="form-label"></label>
+                    <span asp-validation-for="Input.PhoneNumber" class="text-danger"></span>
+                </div>
+                <button id="update-profile-button" type="submit" class="w-100 btn btn-primary">저장</button>
+            </form>
+        </div>
+    </div>
+</div>
+
+@section Scripts {
+    <partial name="_ValidationScriptsPartial" />
+}

+ 118 - 0
backend/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs

@@ -0,0 +1,118 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Text.Encodings.Web;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace bitforum.Areas.Identity.Pages.Account.Manage
+{
+    public class IndexModel : PageModel
+    {
+        private readonly UserManager<IdentityUser> _userManager;
+        private readonly SignInManager<IdentityUser> _signInManager;
+
+        public IndexModel(
+            UserManager<IdentityUser> userManager,
+            SignInManager<IdentityUser> signInManager)
+        {
+            _userManager = userManager;
+            _signInManager = signInManager;
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public string Username { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [TempData]
+        public string StatusMessage { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [BindProperty]
+        public InputModel Input { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public class InputModel
+        {
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [Phone]
+            [Display(Name = "Phone number")]
+            public string PhoneNumber { get; set; }
+        }
+
+        private async Task LoadAsync(IdentityUser user)
+        {
+            var userName = await _userManager.GetUserNameAsync(user);
+            var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
+
+            Username = userName;
+
+            Input = new InputModel
+            {
+                PhoneNumber = phoneNumber
+            };
+        }
+
+        public async Task<IActionResult> OnGetAsync()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            await LoadAsync(user);
+            return Page();
+        }
+
+        public async Task<IActionResult> OnPostAsync()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            if (!ModelState.IsValid)
+            {
+                await LoadAsync(user);
+                return Page();
+            }
+
+            var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
+            if (Input.PhoneNumber != phoneNumber)
+            {
+                var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber);
+                if (!setPhoneResult.Succeeded)
+                {
+                    StatusMessage = "Unexpected error when trying to set phone number.";
+                    return RedirectToPage();
+                }
+            }
+
+            await _signInManager.RefreshSignInAsync(user);
+            StatusMessage = "Your profile has been updated";
+            return RedirectToPage();
+        }
+    }
+}

+ 123 - 0
backend/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs

@@ -0,0 +1,123 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using Microsoft.AspNetCore.Mvc.Rendering;
+
+namespace  bitforum.Areas.Identity.Pages.Account.Manage
+{
+    /// <summary>
+    ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+    ///     directly from your code. This API may change or be removed in future releases.
+    /// </summary>
+    public static class ManageNavPages
+    {
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public static string Index => "Index";
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public static string Email => "Email";
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public static string ChangePassword => "ChangePassword";
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public static string DownloadPersonalData => "DownloadPersonalData";
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public static string DeletePersonalData => "DeletePersonalData";
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public static string ExternalLogins => "ExternalLogins";
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public static string PersonalData => "PersonalData";
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public static string TwoFactorAuthentication => "TwoFactorAuthentication";
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public static string EmailNavClass(ViewContext viewContext) => PageNavClass(viewContext, Email);
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword);
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public static string DownloadPersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DownloadPersonalData);
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public static string DeletePersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DeletePersonalData);
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins);
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public static string PersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, PersonalData);
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication);
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public static string PageNavClass(ViewContext viewContext, string page)
+        {
+            var activePage = viewContext.ViewData["ActivePage"] as string
+                ?? System.IO.Path.GetFileNameWithoutExtension(viewContext.ActionDescriptor.DisplayName);
+            return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null;
+        }
+    }
+}

+ 29 - 0
backend/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml

@@ -0,0 +1,29 @@
+@page
+@model PersonalDataModel
+@{
+    ViewData["Title"] = "Personal Data";
+    ViewData["ActivePage"] = ManageNavPages.PersonalData;
+}
+
+<div class="row">
+    <div class="col">
+        <p>귀하의 계정에는 귀하가 당사에 제공한 개인 데이터가 포함되어 있습니다. <br/>이 페이지에서 해당 데이터를 다운로드하거나 삭제할 수 있습니다.</p>
+        <p>
+            <strong>회원탈퇴 후 귀하의 계정이 영구적으로 삭제되며 복구할 수 없습니다.</strong>
+        </p>
+        <div class="row g-2">
+            <div class="col-auto">
+                <form id="download-data" asp-page="DownloadPersonalData" method="post">
+                    <button class="btn btn-primary" type="submit">다운로드</button>
+                </form>
+            </div>
+            <div class="col-auto">
+                <a id="delete" asp-page="DeletePersonalData" class="btn btn-danger">회원탈퇴</a>
+            </div>
+        </div>
+    </div>
+</div>
+
+@section Scripts {
+    <partial name="_ValidationScriptsPartial" />
+}

+ 36 - 0
backend/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs

@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+
+namespace bitforum.Areas.Identity.Pages.Account.Manage
+{
+    public class PersonalDataModel : PageModel
+    {
+        private readonly UserManager<IdentityUser> _userManager;
+        private readonly ILogger<PersonalDataModel> _logger;
+
+        public PersonalDataModel(
+            UserManager<IdentityUser> userManager,
+            ILogger<PersonalDataModel> logger)
+        {
+            _userManager = userManager;
+            _logger = logger;
+        }
+
+        public async Task<IActionResult> OnGet()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            return Page();
+        }
+    }
+}

+ 24 - 0
backend/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml

@@ -0,0 +1,24 @@
+@page
+@model ResetAuthenticatorModel
+@{
+    ViewData["Title"] = "Reset authenticator key";
+    ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
+}
+
+<partial name="_StatusMessage" for="StatusMessage" />
+<h3>@ViewData["Title"]</h3>
+<div class="alert alert-warning" role="alert">
+    <p>
+        <span class="glyphicon glyphicon-warning-sign"></span>
+        <strong>If you reset your authenticator key your authenticator app will not work until you reconfigure it.</strong>
+    </p>
+    <p>
+        This process disables 2FA until you verify your authenticator app.
+        If you do not complete your authenticator app configuration you may lose access to your account.
+    </p>
+</div>
+<div>
+    <form id="reset-authenticator-form" method="post">
+        <button id="reset-authenticator-button" class="btn btn-danger" type="submit">Reset authenticator key</button>
+    </form>
+</div>

+ 67 - 0
backend/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml.cs

@@ -0,0 +1,67 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+
+namespace bitforum.Areas.Identity.Pages.Account.Manage
+{
+    public class ResetAuthenticatorModel : PageModel
+    {
+        private readonly UserManager<IdentityUser> _userManager;
+        private readonly SignInManager<IdentityUser> _signInManager;
+        private readonly ILogger<ResetAuthenticatorModel> _logger;
+
+        public ResetAuthenticatorModel(
+            UserManager<IdentityUser> userManager,
+            SignInManager<IdentityUser> signInManager,
+            ILogger<ResetAuthenticatorModel> logger)
+        {
+            _userManager = userManager;
+            _signInManager = signInManager;
+            _logger = logger;
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [TempData]
+        public string StatusMessage { get; set; }
+
+        public async Task<IActionResult> OnGet()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            return Page();
+        }
+
+        public async Task<IActionResult> OnPostAsync()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            await _userManager.SetTwoFactorEnabledAsync(user, false);
+            await _userManager.ResetAuthenticatorKeyAsync(user);
+            var userId = await _userManager.GetUserIdAsync(user);
+            _logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", user.Id);
+
+            await _signInManager.RefreshSignInAsync(user);
+            StatusMessage = "Your authenticator app key has been reset, you will need to configure your authenticator app using the new key.";
+
+            return RedirectToPage("./EnableAuthenticator");
+        }
+    }
+}

+ 35 - 0
backend/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml

@@ -0,0 +1,35 @@
+@page
+@model SetPasswordModel
+@{
+    ViewData["Title"] = "Set password";
+    ViewData["ActivePage"] = ManageNavPages.ChangePassword;
+}
+
+<h3>Set your password</h3>
+<partial name="_StatusMessage" for="StatusMessage" />
+<p class="text-info">
+    You do not have a local username/password for this site. Add a local
+    account so you can log in without an external login.
+</p>
+<div class="row">
+    <div class="col-md-6">
+        <form id="set-password-form" method="post">
+            <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
+            <div class="form-floating mb-3">
+                <input asp-for="Input.NewPassword" class="form-control" autocomplete="new-password" placeholder="Please enter your new password."/>
+                <label asp-for="Input.NewPassword" class="form-label"></label>
+                <span asp-validation-for="Input.NewPassword" class="text-danger"></span>
+            </div>
+            <div class="form-floating mb-3">
+                <input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" placeholder="Please confirm your new password."/>
+                <label asp-for="Input.ConfirmPassword" class="form-label"></label>
+                <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
+            </div>
+            <button type="submit" class="w-100 btn btn-lg btn-primary">Set password</button>
+        </form>
+    </div>
+</div>
+
+@section Scripts {
+    <partial name="_ValidationScriptsPartial" />
+}

+ 114 - 0
backend/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml.cs

@@ -0,0 +1,114 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace bitforum.Areas.Identity.Pages.Account.Manage
+{
+    public class SetPasswordModel : PageModel
+    {
+        private readonly UserManager<IdentityUser> _userManager;
+        private readonly SignInManager<IdentityUser> _signInManager;
+
+        public SetPasswordModel(
+            UserManager<IdentityUser> userManager,
+            SignInManager<IdentityUser> signInManager)
+        {
+            _userManager = userManager;
+            _signInManager = signInManager;
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [BindProperty]
+        public InputModel Input { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [TempData]
+        public string StatusMessage { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public class InputModel
+        {
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [Required]
+            [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
+            [DataType(DataType.Password)]
+            [Display(Name = "New password")]
+            public string NewPassword { get; set; }
+
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [DataType(DataType.Password)]
+            [Display(Name = "Confirm new password")]
+            [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
+            public string ConfirmPassword { get; set; }
+        }
+
+        public async Task<IActionResult> OnGetAsync()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            var hasPassword = await _userManager.HasPasswordAsync(user);
+
+            if (hasPassword)
+            {
+                return RedirectToPage("./ChangePassword");
+            }
+
+            return Page();
+        }
+
+        public async Task<IActionResult> OnPostAsync()
+        {
+            if (!ModelState.IsValid)
+            {
+                return Page();
+            }
+
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            var addPasswordResult = await _userManager.AddPasswordAsync(user, Input.NewPassword);
+            if (!addPasswordResult.Succeeded)
+            {
+                foreach (var error in addPasswordResult.Errors)
+                {
+                    ModelState.AddModelError(string.Empty, error.Description);
+                }
+                return Page();
+            }
+
+            await _signInManager.RefreshSignInAsync(user);
+            StatusMessage = "Your password has been set.";
+
+            return RedirectToPage();
+        }
+    }
+}

+ 25 - 0
backend/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml

@@ -0,0 +1,25 @@
+@page
+@model ShowRecoveryCodesModel
+@{
+    ViewData["Title"] = "Recovery codes";
+    ViewData["ActivePage"] = "TwoFactorAuthentication";
+}
+
+<partial name="_StatusMessage" for="StatusMessage" />
+<h3>@ViewData["Title"]</h3>
+<div class="alert alert-warning" role="alert">
+    <p>
+        <strong>Put these codes in a safe place.</strong>
+    </p>
+    <p>
+        If you lose your device and don't have the recovery codes you will lose access to your account.
+    </p>
+</div>
+<div class="row">
+    <div class="col-md-12">
+        @for (var row = 0; row < Model.RecoveryCodes.Length; row += 2)
+        {
+            <code class="recovery-code">@Model.RecoveryCodes[row]</code><text>&nbsp;</text><code class="recovery-code">@Model.RecoveryCodes[row + 1]</code><br />
+        }
+    </div>
+</div>

+ 46 - 0
backend/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml.cs

@@ -0,0 +1,46 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+
+namespace bitforum.Areas.Identity.Pages.Account.Manage
+{
+    /// <summary>
+    ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+    ///     directly from your code. This API may change or be removed in future releases.
+    /// </summary>
+    public class ShowRecoveryCodesModel : PageModel
+    {
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [TempData]
+        public string[] RecoveryCodes { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [TempData]
+        public string StatusMessage { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public IActionResult OnGet()
+        {
+            if (RecoveryCodes == null || RecoveryCodes.Length == 0)
+            {
+                return RedirectToPage("./TwoFactorAuthentication");
+            }
+
+            return Page();
+        }
+    }
+}

+ 71 - 0
backend/Areas/Identity/Pages/Account/Manage/TwoFactorAuthentication.cshtml

@@ -0,0 +1,71 @@
+@page
+@using Microsoft.AspNetCore.Http.Features
+@model TwoFactorAuthenticationModel
+@{
+    ViewData["Title"] = "Two-factor authentication (2FA)";
+    ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
+}
+
+<partial name="_StatusMessage" for="StatusMessage" />
+<h3>@ViewData["Title"]</h3>
+@{
+    var consentFeature = HttpContext.Features.Get<ITrackingConsentFeature>();
+    @if (consentFeature?.CanTrack ?? true)
+    {
+        @if (Model.Is2faEnabled)
+        {
+            if (Model.RecoveryCodesLeft == 0)
+            {
+                <div class="alert alert-danger">
+                    <strong>You have no recovery codes left.</strong>
+                    <p>You must <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a> before you can log in with a recovery code.</p>
+                </div>
+            }
+            else if (Model.RecoveryCodesLeft == 1)
+            {
+                <div class="alert alert-danger">
+                    <strong>You have 1 recovery code left.</strong>
+                    <p>You can <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a>.</p>
+                </div>
+            }
+            else if (Model.RecoveryCodesLeft <= 3)
+            {
+                <div class="alert alert-warning">
+                    <strong>You have @Model.RecoveryCodesLeft recovery codes left.</strong>
+                    <p>You should <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a>.</p>
+                </div>
+            }
+
+            if (Model.IsMachineRemembered)
+            {
+                <form method="post" style="display: inline-block">
+                    <button type="submit" class="btn btn-primary">Forget this browser</button>
+                </form>
+            }
+            <a asp-page="./Disable2fa" class="btn btn-primary">Disable 2FA</a>
+            <a asp-page="./GenerateRecoveryCodes" class="btn btn-primary">Reset recovery codes</a>
+        }
+
+        <h4>Authenticator app</h4>
+        @if (!Model.HasAuthenticator)
+        {
+            <a id="enable-authenticator" asp-page="./EnableAuthenticator" class="btn btn-primary">Add authenticator app</a>
+        }
+        else
+        {
+            <a id="enable-authenticator" asp-page="./EnableAuthenticator" class="btn btn-primary">Set up authenticator app</a>
+            <a id="reset-authenticator" asp-page="./ResetAuthenticator" class="btn btn-primary">Reset authenticator app</a>
+        }
+    }
+    else
+    {
+        <div class="alert alert-danger">
+            <strong>Privacy and cookie policy have not been accepted.</strong>
+            <p>You must accept the policy before you can enable two factor authentication.</p>
+        </div>
+    }
+}
+
+@section Scripts {
+    <partial name="_ValidationScriptsPartial" />
+}

+ 89 - 0
backend/Areas/Identity/Pages/Account/Manage/TwoFactorAuthentication.cshtml.cs

@@ -0,0 +1,89 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.Extensions.Logging;
+
+namespace bitforum.Areas.Identity.Pages.Account.Manage
+{
+    public class TwoFactorAuthenticationModel : PageModel
+    {
+        private readonly UserManager<IdentityUser> _userManager;
+        private readonly SignInManager<IdentityUser> _signInManager;
+        private readonly ILogger<TwoFactorAuthenticationModel> _logger;
+
+        public TwoFactorAuthenticationModel(
+            UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager, ILogger<TwoFactorAuthenticationModel> logger)
+        {
+            _userManager = userManager;
+            _signInManager = signInManager;
+            _logger = logger;
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public bool HasAuthenticator { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public int RecoveryCodesLeft { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [BindProperty]
+        public bool Is2faEnabled { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public bool IsMachineRemembered { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [TempData]
+        public string StatusMessage { get; set; }
+
+        public async Task<IActionResult> OnGetAsync()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            HasAuthenticator = await _userManager.GetAuthenticatorKeyAsync(user) != null;
+            Is2faEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
+            IsMachineRemembered = await _signInManager.IsTwoFactorClientRememberedAsync(user);
+            RecoveryCodesLeft = await _userManager.CountRecoveryCodesAsync(user);
+
+            return Page();
+        }
+
+        public async Task<IActionResult> OnPostAsync()
+        {
+            var user = await _userManager.GetUserAsync(User);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
+            }
+
+            await _signInManager.ForgetTwoFactorClientAsync();
+            StatusMessage = "The current browser has been forgotten. When you login again from this browser you will be prompted for your 2fa code.";
+            return RedirectToPage();
+        }
+    }
+}

+ 29 - 0
backend/Areas/Identity/Pages/Account/Manage/_Layout.cshtml

@@ -0,0 +1,29 @@
+@{
+    if (ViewData.TryGetValue("ParentLayout", out var parentLayout) && parentLayout !=  null)
+    {
+        Layout = parentLayout.ToString();
+    }
+    else
+    {
+        Layout = "/Areas/Identity/Pages/_Layout.cshtml";
+    }
+}
+
+<div class="container">
+    <div class="pt-3">
+        <h5>내 정보</h5>
+        <hr />
+        <div class="row">
+            <div class="col-md-3">
+                <partial name="_ManageNav" />
+            </div>
+            <div class="col-md-9">
+                @RenderBody()
+            </div>
+        </div>
+    </div>
+</div>
+
+@section Scripts {
+    @RenderSection("Scripts", required: false)
+}

+ 17 - 0
backend/Areas/Identity/Pages/Account/Manage/_ManageNav.cshtml

@@ -0,0 +1,17 @@
+@inject SignInManager<IdentityUser> SignInManager
+@{
+    var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any();
+}
+<ul class="nav nav-underline flex-column">
+    <li class="nav-item"><a class="nav-link @ManageNavPages.IndexNavClass(ViewContext)" id="profile" asp-page="./Index">기본 정보</a></li>
+    <li class="nav-item"><a class="nav-link @ManageNavPages.EmailNavClass(ViewContext)" id="email" asp-page="./Email">이메일 변경</a></li>
+    <li class="nav-item"><a class="nav-link @ManageNavPages.ChangePasswordNavClass(ViewContext)" id="change-password" asp-page="./ChangePassword">비밀번호 변경</a></li>
+    @if (hasExternalLogins)
+    {
+        <li id="external-logins" class="nav-item"><a id="external-login" class="nav-link @ManageNavPages.ExternalLoginsNavClass(ViewContext)" asp-page="./ExternalLogins">External logins</a></li>
+    }
+    @*
+    <li class="nav-item"><a class="nav-link @ManageNavPages.TwoFactorAuthenticationNavClass(ViewContext)" id="two-factor" asp-page="./TwoFactorAuthentication">Two-factor authentication</a></li>
+    *@
+    <li class="nav-item"><a class="nav-link @ManageNavPages.PersonalDataNavClass(ViewContext)" id="personal-data" asp-page="./PersonalData">개인정보</a></li>
+</ul>

+ 10 - 0
backend/Areas/Identity/Pages/Account/Manage/_StatusMessage.cshtml

@@ -0,0 +1,10 @@
+@model string
+
+@if (!String.IsNullOrEmpty(Model))
+{
+    var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success";
+    <div class="alert alert-@statusMessageClass alert-dismissible" role="alert">
+        <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+        @Model
+    </div>
+}

+ 1 - 0
backend/Areas/Identity/Pages/Account/Manage/_ViewImports.cshtml

@@ -0,0 +1 @@
+@using bitforum.Areas.Identity.Pages.Account.Manage

+ 81 - 0
backend/Areas/Identity/Pages/Account/Register.cshtml

@@ -0,0 +1,81 @@
+@page
+@model RegisterModel
+@{
+    ViewData["Title"] = "회원가입";
+}
+
+<div id="registForm" class="row row-cols-1 justify-content-center align-items-center min-vh-100">
+    <div class="col col-12 col-sm-7 col-md-5 col-lg-5 col-xl-3">
+        <section class="border rounded p-3">
+            <h4>@ViewData["Title"]</h4>
+            <form id="registerForm" asp-route-returnUrl="@Model.ReturnUrl" method="post" accept-charset="utf-8" autocomplete="off">
+                <small>가입 후 이메일 인증이 필요하며 관리자의 <br/>최종 승인 후 접속이 가능합니다.</small>
+                <hr />
+                <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
+                <div class="form-floating mb-3">
+                    <input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com" />
+                    <label asp-for="Input.Email">Email</label>
+                    <span asp-validation-for="Input.Email" class="text-danger"></span>
+                </div>
+                <div class="form-floating mb-3">
+                    <input asp-for="Input.Password" class="form-control" autocomplete="new-password" aria-required="true" placeholder="password" />
+                    <label asp-for="Input.Password">Password</label>
+                    <span asp-validation-for="Input.Password" class="text-danger"></span>
+                </div>
+                <div class="form-floating mb-3">
+                    <input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" aria-required="true" placeholder="password" />
+                    <label asp-for="Input.ConfirmPassword">Confirm Password</label>
+                    <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
+                </div>
+                <button id="registerSubmit" type="submit" class="w-100 btn btn-primary">회원가입</button>
+                <p class="pt-3 text-center">
+                    <a asp-page="./Login" asp-route-returnUrl="@Model.ReturnUrl">< 취소하기</a>
+                </p>
+            </form>
+        </section>
+    </div>
+    <div class="col">
+        <div class="text-center ps-3 pe-3">
+            <hr />
+            <small>ⓒ PLAYR. All Rights Reserved</small>
+        </div>
+        @*
+        <section>
+            <h3>Use another service to register.</h3>
+            <hr />
+            @{
+                if ((Model.ExternalLogins?.Count ?? 0) == 0)
+                {
+                    <div>
+                        <p>
+                            There are no external authentication services configured. See this <a href="https://go.microsoft.com/fwlink/?LinkID=532715">article
+                            about setting up this ASP.NET application to support logging in via external services</a>.
+                        </p>
+                    </div>
+                }
+                else
+                {
+                    <form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
+                        <div>
+                            <p>
+                                @foreach (var provider in Model.ExternalLogins!)
+                                {
+                                    <button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
+                                }
+                            </p>
+                        </div>
+                    </form>
+                }
+            }
+        </section>
+        *@
+    </div>
+</div>
+
+@section Scripts {
+    <partial name="_ValidationScriptsPartial" />
+}
+
+@section Styles {
+    <link rel="stylesheet" href="~/css/account.css" asp-append-version="true" />
+}

+ 180 - 0
backend/Areas/Identity/Pages/Account/Register.cshtml.cs

@@ -0,0 +1,180 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text;
+using System.Text.Encodings.Web;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Identity.UI.Services;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.WebUtilities;
+using Microsoft.Extensions.Logging;
+
+namespace bitforum.Areas.Identity.Pages.Account
+{
+    public class RegisterModel : PageModel
+    {
+        private readonly SignInManager<IdentityUser> _signInManager;
+        private readonly UserManager<IdentityUser> _userManager;
+        private readonly IUserStore<IdentityUser> _userStore;
+        private readonly IUserEmailStore<IdentityUser> _emailStore;
+        private readonly ILogger<RegisterModel> _logger;
+        private readonly IEmailSender _emailSender;
+
+        public RegisterModel(
+            UserManager<IdentityUser> userManager,
+            IUserStore<IdentityUser> userStore,
+            SignInManager<IdentityUser> signInManager,
+            ILogger<RegisterModel> logger,
+            IEmailSender emailSender)
+        {
+            _userManager = userManager;
+            _userStore = userStore;
+            _emailStore = GetEmailStore();
+            _signInManager = signInManager;
+            _logger = logger;
+            _emailSender = emailSender;
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [BindProperty]
+        public InputModel Input { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public string ReturnUrl { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public IList<AuthenticationScheme> ExternalLogins { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public class InputModel
+        {
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [Required]
+            [EmailAddress]
+            [Display(Name = "Email")]
+            public string Email { get; set; }
+
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [Required]
+            [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
+            [DataType(DataType.Password)]
+            [Display(Name = "Password")]
+            public string Password { get; set; }
+
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [DataType(DataType.Password)]
+            [Display(Name = "Confirm password")]
+            [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
+            public string ConfirmPassword { get; set; }
+        }
+
+
+        public async Task OnGetAsync(string returnUrl = null)
+        {
+            ReturnUrl = returnUrl;
+            ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
+        }
+
+        public async Task<IActionResult> OnPostAsync(string returnUrl = null)
+        {
+            returnUrl ??= Url.Content("~/");
+            ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
+            if (ModelState.IsValid)
+            {
+                var user = CreateUser();
+
+                await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
+                await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
+                var result = await _userManager.CreateAsync(user, Input.Password);
+
+                if (result.Succeeded)
+                {
+                    _logger.LogInformation("User created a new account with password.");
+
+                    var userId = await _userManager.GetUserIdAsync(user);
+                    var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
+                    code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
+                    var callbackUrl = Url.Page(
+                        "/Account/ConfirmEmail",
+                        pageHandler: null,
+                        values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
+                        protocol: Request.Scheme);
+
+                    await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
+                        $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
+
+                    if (_userManager.Options.SignIn.RequireConfirmedAccount)
+                    {
+                        return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl });
+                    }
+                    else
+                    {
+                        await _signInManager.SignInAsync(user, isPersistent: false);
+                        return LocalRedirect(returnUrl);
+                    }
+                }
+                foreach (var error in result.Errors)
+                {
+                    ModelState.AddModelError(string.Empty, error.Description);
+                }
+            }
+
+            // If we got this far, something failed, redisplay form
+            return Page();
+        }
+
+        private IdentityUser CreateUser()
+        {
+            try
+            {
+                return Activator.CreateInstance<IdentityUser>();
+            }
+            catch
+            {
+                throw new InvalidOperationException($"Can't create an instance of '{nameof(IdentityUser)}'. " +
+                    $"Ensure that '{nameof(IdentityUser)}' is not an abstract class and has a parameterless constructor, or alternatively " +
+                    $"override the register page in /Areas/Identity/Pages/Account/Register.cshtml");
+            }
+        }
+
+        private IUserEmailStore<IdentityUser> GetEmailStore()
+        {
+            if (!_userManager.SupportsUserEmail)
+            {
+                throw new NotSupportedException("The default UI requires a user store with email support.");
+            }
+            return (IUserEmailStore<IdentityUser>)_userStore;
+        }
+    }
+}

+ 29 - 0
backend/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml

@@ -0,0 +1,29 @@
+@page
+@model RegisterConfirmationModel
+@{
+    ViewData["Title"] = "Register confirmation";
+}
+
+@*
+<h1>@ViewData["Title"]</h1>
+@{
+    if (@Model.DisplayConfirmAccountLink)
+    {
+<p>
+    This app does not currently have a real email sender registered, see <a href="https://aka.ms/aspaccountconf">these docs</a> for how to configure a real email sender.
+    Normally this would be emailed: <a id="confirm-link" href="@Model.EmailConfirmationUrl">Click here to confirm your account</a>
+</p>
+    }
+    else
+    {
+<p>
+        Please check your email to confirm your account.
+</p>
+    }
+}
+*@
+
+<script>
+    alert("이메일 확인 메일을 전송했습니다. 인증 확인 후 접속이 가능합니다.");
+    window.location.replace("/Identity/Account/Login");
+</script>

+ 79 - 0
backend/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs

@@ -0,0 +1,79 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Identity.UI.Services;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.WebUtilities;
+
+namespace bitforum.Areas.Identity.Pages.Account
+{
+    [AllowAnonymous]
+    public class RegisterConfirmationModel : PageModel
+    {
+        private readonly UserManager<IdentityUser> _userManager;
+        private readonly IEmailSender _sender;
+
+        public RegisterConfirmationModel(UserManager<IdentityUser> userManager, IEmailSender sender)
+        {
+            _userManager = userManager;
+            _sender = sender;
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public string Email { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public bool DisplayConfirmAccountLink { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public string EmailConfirmationUrl { get; set; }
+
+        public async Task<IActionResult> OnGetAsync(string email, string returnUrl = null)
+        {
+            if (email == null)
+            {
+                return RedirectToPage("/Index");
+            }
+            returnUrl = returnUrl ?? Url.Content("~/");
+
+            var user = await _userManager.FindByEmailAsync(email);
+            if (user == null)
+            {
+                return NotFound($"Unable to load user with email '{email}'.");
+            }
+
+            Email = email;
+            // Once you add a real email sender, you should remove this code that lets you confirm the account
+            DisplayConfirmAccountLink = true;
+            if (DisplayConfirmAccountLink)
+            {
+                var userId = await _userManager.GetUserIdAsync(user);
+                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
+                code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
+                EmailConfirmationUrl = Url.Page(
+                    "/Account/ConfirmEmail",
+                    pageHandler: null,
+                    values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
+                    protocol: Request.Scheme);
+            }
+
+            return Page();
+        }
+    }
+}

+ 41 - 0
backend/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml

@@ -0,0 +1,41 @@
+@page
+@model ResendEmailConfirmationModel
+@{
+    ViewData["Title"] = "이메일 인증 재전송";
+}
+
+<div id="resendEmailConfirmForm" class="row row-cols-1 justify-content-center align-items-center min-vh-100">
+    <div class="col col-12 col-sm-7 col-md-5 col-lg-5 col-xl-3">
+        <section class="border rounded p-3">
+            <form method="post" accept-charset="utf-8" autocomplete="off">
+                <small class="d-block pb-2">@ViewData["Title"]</small>
+                <h5>가입 시 이메일을 입력하세요.</h5>
+                <hr />
+                <div asp-validation-summary="All" class="text-danger" role="alert"></div>
+                <div class="form-floating mb-3">
+                    <input asp-for="Input.Email" class="form-control" aria-required="true" placeholder="name@example.com" />
+                    <label asp-for="Input.Email" class="form-label"></label>
+                    <span asp-validation-for="Input.Email" class="text-danger"></span>
+                </div>
+                <button type="submit" class="w-100 btn btn-primary">재전송</button>
+                <p class="pt-3 text-center">
+                    <a asp-page="./Login">< 취소하기</a>
+                </p>
+            </form>
+        </section>
+    </div>
+    <div class="col">
+        <div class="text-center ps-3 pe-3">
+            <hr />
+            <small>ⓒ PLAYR. All Rights Reserved</small>
+        </div>
+    </div>
+</div>
+
+@section Scripts {
+    <partial name="_ValidationScriptsPartial" />
+}
+
+@section Styles {
+    <link rel="stylesheet" href="~/css/account.css" asp-append-version="true" />
+}

+ 88 - 0
backend/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml.cs

@@ -0,0 +1,88 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Text;
+using System.Text.Encodings.Web;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Identity.UI.Services;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.WebUtilities;
+
+namespace bitforum.Areas.Identity.Pages.Account
+{
+    [AllowAnonymous]
+    public class ResendEmailConfirmationModel : PageModel
+    {
+        private readonly UserManager<IdentityUser> _userManager;
+        private readonly IEmailSender _emailSender;
+
+        public ResendEmailConfirmationModel(UserManager<IdentityUser> userManager, IEmailSender emailSender)
+        {
+            _userManager = userManager;
+            _emailSender = emailSender;
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [BindProperty]
+        public InputModel Input { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public class InputModel
+        {
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [Required]
+            [EmailAddress]
+            public string Email { get; set; }
+        }
+
+        public void OnGet()
+        {
+        }
+
+        public async Task<IActionResult> OnPostAsync()
+        {
+            if (!ModelState.IsValid)
+            {
+                return Page();
+            }
+
+            var user = await _userManager.FindByEmailAsync(Input.Email);
+            if (user == null)
+            {
+                ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email.");
+                return Page();
+            }
+
+            var userId = await _userManager.GetUserIdAsync(user);
+            var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
+            code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
+            var callbackUrl = Url.Page(
+                "/Account/ConfirmEmail",
+                pageHandler: null,
+                values: new { userId = userId, code = code },
+                protocol: Request.Scheme);
+            await _emailSender.SendEmailAsync(
+                Input.Email,
+                "Confirm your email",
+                $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
+
+            ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email.");
+            return Page();
+        }
+    }
+}

+ 48 - 0
backend/Areas/Identity/Pages/Account/ResetPassword.cshtml

@@ -0,0 +1,48 @@
+@page
+@model ResetPasswordModel
+@{
+    ViewData["Title"] = "비밀번호 변경";
+}
+
+<div id="resetPasswordForm" class="row row-cols-1 justify-content-center align-items-center min-vh-100">
+    <div class="col col-12 col-sm-7 col-md-5 col-lg-5 col-xl-3">
+        <section class="border rounded p-3">
+            <form method="post" accept-charset="utf-8" autocomplete="off">
+                <small class="d-block pb-2">@ViewData["Title"]</small>
+                <h5>비밀번호를 재설정 합니다.</h5>
+                <hr />
+                <div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
+                <input asp-for="Input.Code" type="hidden" />
+                <div class="form-floating mb-3">
+                    <input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com" />
+                    <label asp-for="Input.Email" class="form-label"></label>
+                    <span asp-validation-for="Input.Email" class="text-danger"></span>
+                </div>
+                <div class="form-floating mb-3">
+                    <input asp-for="Input.Password" class="form-control" autocomplete="new-password" aria-required="true" placeholder="Please enter your password." />
+                    <label asp-for="Input.Password" class="form-label"></label>
+                    <span asp-validation-for="Input.Password" class="text-danger"></span>
+                </div>
+                <div class="form-floating mb-3">
+                    <input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" aria-required="true" placeholder="Please confirm your password." />
+                    <label asp-for="Input.ConfirmPassword" class="form-label"></label>
+                    <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
+                </div>
+                <button type="submit" class="w-100 btn btn-primary">변경하기</button>
+                <p class="pt-3 text-center">
+                    <a asp-page="./Login">< 취소하기</a>
+                </p>
+            </form>
+        </section>
+    </div>
+    <div class="col">
+        <div class="text-center ps-3 pe-3">
+            <hr />
+            <small>ⓒ PLAYR. All Rights Reserved</small>
+        </div>
+    </div>
+</div>
+
+@section Scripts {
+    <partial name="_ValidationScriptsPartial" />
+}

+ 117 - 0
backend/Areas/Identity/Pages/Account/ResetPassword.cshtml.cs

@@ -0,0 +1,117 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using System;
+using System.ComponentModel.DataAnnotations;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.WebUtilities;
+
+namespace bitforum.Areas.Identity.Pages.Account
+{
+    public class ResetPasswordModel : PageModel
+    {
+        private readonly UserManager<IdentityUser> _userManager;
+
+        public ResetPasswordModel(UserManager<IdentityUser> userManager)
+        {
+            _userManager = userManager;
+        }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        [BindProperty]
+        public InputModel Input { get; set; }
+
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public class InputModel
+        {
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [Required]
+            [EmailAddress]
+            public string Email { get; set; }
+
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [Required]
+            [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
+            [DataType(DataType.Password)]
+            public string Password { get; set; }
+
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [DataType(DataType.Password)]
+            [Display(Name = "Confirm password")]
+            [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
+            public string ConfirmPassword { get; set; }
+
+            /// <summary>
+            ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+            ///     directly from your code. This API may change or be removed in future releases.
+            /// </summary>
+            [Required]
+            public string Code { get; set; }
+
+        }
+
+        public IActionResult OnGet(string code = null)
+        {
+            if (code == null)
+            {
+                return BadRequest("A code must be supplied for password reset.");
+            }
+            else
+            {
+                Input = new InputModel
+                {
+                    Code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code))
+                };
+                return Page();
+            }
+        }
+
+        public async Task<IActionResult> OnPostAsync()
+        {
+            if (!ModelState.IsValid)
+            {
+                return Page();
+            }
+
+            var user = await _userManager.FindByEmailAsync(Input.Email);
+            if (user == null)
+            {
+                // Don't reveal that the user does not exist
+                return RedirectToPage("./ResetPasswordConfirmation");
+            }
+
+            var result = await _userManager.ResetPasswordAsync(user, Input.Code, Input.Password);
+            if (result.Succeeded)
+            {
+                return RedirectToPage("./ResetPasswordConfirmation");
+            }
+
+            foreach (var error in result.Errors)
+            {
+                ModelState.AddModelError(string.Empty, error.Description);
+            }
+            return Page();
+        }
+    }
+}

+ 17 - 0
backend/Areas/Identity/Pages/Account/ResetPasswordConfirmation.cshtml

@@ -0,0 +1,17 @@
+@page
+@model ResetPasswordConfirmationModel
+@{
+    ViewData["Title"] = "Reset password confirmation";
+}
+
+@*
+<h1>@ViewData["Title"]</h1>
+<p>
+    Your password has been reset. Please <a asp-page="./Login">click here to log in</a>.
+</p>
+*@
+
+<script>
+    alert("비밀번호가 정상적으로 변경되었습니다.");
+    window.location.replace("/Identity/Account/Login");
+</script>

+ 25 - 0
backend/Areas/Identity/Pages/Account/ResetPasswordConfirmation.cshtml.cs

@@ -0,0 +1,25 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+#nullable disable
+
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace bitforum.Areas.Identity.Pages.Account
+{
+    /// <summary>
+    ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+    ///     directly from your code. This API may change or be removed in future releases.
+    /// </summary>
+    [AllowAnonymous]
+    public class ResetPasswordConfirmationModel : PageModel
+    {
+        /// <summary>
+        ///     This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
+        ///     directly from your code. This API may change or be removed in future releases.
+        /// </summary>
+        public void OnGet()
+        {
+        }
+    }
+}

+ 10 - 0
backend/Areas/Identity/Pages/Account/_StatusMessage.cshtml

@@ -0,0 +1,10 @@
+@model string
+
+@if (!String.IsNullOrEmpty(Model))
+{
+    var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success";
+    <div class="alert alert-@statusMessageClass alert-dismissible" role="alert">
+        <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+        @Model
+    </div>
+}

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott