Формування безопасного токена для зміни паролю.
Попередник функцій, описаних на цій сторінці, був описан у мене п'ять років тому ось тут - Избавляемся от базы стандартных пользователей ASP.NET на MS SQL - пример ASP.NET сайта на MySQL - на цей сторінці описана незламна та достатньо надійна технология, але з роками і вона була модифікована у кращу сторону.
Функція надійної зміни паролю є найважливіша, мені відомо що навіть великі банківські системи мають уразливість на атаку на зміну паролей, наприклад гаманець KIWI - це не гаманець зовсім, а дірка від гаманця, бо робили цю функцію дурні. Тому я тут і опишу, як це треба робити корректно.
Загальний принцип формування безопасного токену (тобто при використовуванні на сайті це просто особливим чином сформований URL) полягає у таких моментах:
- По-перше, може бути сформовано декілька послідовних реквестів на зміну пароля. При формуванні нового реквесту - попередній автоматично стає недійсним. У моєму фрейморці це параметр ID, який по схемі URL-реврайтінгу MVC іде наступнім параметром після Action ResetPassword, наприклад на скрині вище, це d5c32203-5045-49ac-8a83-be4be3121cb1. Це одноразовий тікет, кожний наступний реквест на зміну пароля змінює це випадкове число. Вгадати його неможна, тобто взагалі достатньо і цього числа. Так у мене було зроблено раніше. Але зараз я ще покращив щю схему.
- По-друге, масові реквести на грубий підбір цього числа не повинні взагалі приводити до звертання сайта до бази - лише таким чином можно захиститися від подбору цього параметра ID. Для того, щоб сайт не звертався у базу, я добавив в URL другий параметр UID=84193ab6-150e-4578-bc98-d77ea5375a31 (це ID юзера) - саме він дозволяє мені перевіряти коректність третього параметру Token без звертання до бази, тобто "на льоту".
- І третій параметр Token, який міститьMD5-хеш, робить цей URL взагалі незламним. Грубо кажучі, функция Token містить якусь залежність одноразового тікету, ID юзера та параметру web-конфігу SecretConstant (наприклад "3rбF4kzJszie42a"), який з одного боку неможливо вгадати, по-друге його можно змінювати хоча кожен день, а по трете - схема комбінації ID, UID, SecretConstant невідома. А четверте, у цей параметр Token можно зашити також час, що робить час дійсності токена невеликим, наприклад одна година.
- Навіть прослушка цього URL десь на магістралі нічого не дає, бо тікет ID одноразовий, повторне його використання неможливо, якщо тільки встигнути використати цей URL раніше власнику сайта. Крім того, сам одноразовий тікет ID також має невеликий час життя. Але з цим боротися теж можна, зробивши SSL-шифрування до сайту.
Таким чином, модифікація мого попереднього фреймворку полягає в тому, що
- одноразовий тікет видається на невеличкий термін життя
- безпосередньо у URL міститься хеш, який дозволяє перевірити коректність усього SecurityToken'у, тобто URL - без звернення до бази, тобто "на льоту", що покращує стійкість сайту до атак на грубий підбір паролей
- у конфигу сайту зберігається параметр, який можно у будь-який час змінити і цим зробити недійснім разом усі видані сайтом URL на зміну паролю.
Тепер подивимося безпосередньо на код. По-перше, є табличка юзерів Users:
1: CREATE TABLE [dbo].[Users](
2: [i] [int] IDENTITY(1,1) NOT NULL,
3: [ID] [uniqueidentifier] NOT NULL,
4: [FirstName] [nvarchar](50) NOT NULL,
5: [LastName] [nvarchar](50) NOT NULL,
6: [Email] [nvarchar](50) NOT NULL,
7: [Password] [nvarchar](15) NOT NULL,
8: [IsAdmin] [int] NOT NULL,
9: [CrDate] [datetime] NOT NULL,
10: [LastLoginDate] [datetime] NOT NULL,
11: [DailyMail] [int] NULL,
12: [WeeklyMail] [int] NULL,
13: [RestorePassID] [uniqueidentifier] NULL,
14:
15: ....
16:
17: CONSTRAINT [PK_Users] PRIMARY KEY CLUSTERED
18: (
19: [i] ASC
20: )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
21: ) ON [PRIMARY]
22:
23: GO
У якій нам найважливо побачити поле [ID] (це ID юзера) та [RestorePassID] (це одноразовий тікет на зміну пароля).
Ця табличка відмапірована за допомогою мапера Linq-To-SQL на файл OCMR.dbml:
1: <?xml version="1.0" encoding="utf-8"?><Database Name="Ocmr" Class="OCMRDataContext" xmlns="http://schemas.microsoft.com/linqtosql/dbml/2007">
2: <Connection Mode="WebSettings" ConnectionString="Data Source=AAA.BBB.CCC.DDD;Initial Catalog=Ocmr;User ID=Ocmr;Max Pool Size=10000" SettingsObjectName="System.Configuration.ConfigurationManager.ConnectionStrings" SettingsPropertyName="OCMR_ConnectionStrings" Provider="System.Data.SqlClient" />
3: <Table Name="dbo.Users" Member="Users">
4: <Type Name="User">
5: <Column Name="i" Type="System.Int32" DbType="Int NOT NULL IDENTITY" IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" />
6: <Column Name="ID" Type="System.Guid" DbType="UniqueIdentifier NOT NULL" CanBeNull="false" />
7: <Column Name="FirstName" Type="System.String" DbType="NVarChar(50) NOT NULL" CanBeNull="false" />
8: <Column Name="LastName" Type="System.String" DbType="NVarChar(50) NOT NULL" CanBeNull="false" />
9: <Column Name="Email" Type="System.String" DbType="NVarChar(50) NOT NULL" CanBeNull="false" />
10: <Column Name="Password" Type="System.String" DbType="NVarChar(15) NOT NULL" CanBeNull="false" />
11: <Column Name="IsAdmin" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
12: <Column Name="CrDate" Type="System.DateTime" DbType="DateTime NOT NULL" CanBeNull="false" />
13: <Column Name="LastLoginDate" Type="System.DateTime" DbType="DateTime NOT NULL" CanBeNull="false" />
14: <Column Name="DailyMail" Type="System.Int32" DbType="Int" CanBeNull="true" />
15: <Column Name="WeeklyMail" Type="System.Int32" DbType="Int" CanBeNull="true" />
16: <Column Name="RestorePassID" Type="System.Guid" DbType="UniqueIdentifier" CanBeNull="true" />
17:
18: ....
19:
20: </Type>
21: </Table>
22: ....
Також ми маємо у проєкті конфіг, що містить по перше конект до бази (у данному випадку це OCMR_ConnectionStrings, а по-друге той самий параметр SecretConstant:
1: <?xml version="1.0"?>
2:
3: <configuration>
4: <appSettings>
5: ....
6: <add key="SecretConstant" value= "Reset Password "/>
7: ....
8: <add key="CacheImageReiseMode1" value="1"/>
9: <add key="CacheImageReiseMode2" value="2"/>
10: <add key="webpages:Version" value="1.0.0.0"/>
11: <add key="ClientValidationEnabled" value="true"/>
12: <add key="UnobtrusiveJavaScriptEnabled" value="true"/>
13: </appSettings>
14: <connectionStrings>
15: <remove name="LocalSqlServer"/>
16: <add name="OCMR_ConnectionStrings" connectionString="server=AAA.BBB.CCC.DDD;Initial Catalog=Ocmr;User ID=Ocmr;Password=XXXXXXXXXXXXX;Max Pool Size=10000;" providerName="System.Data.SqlClient"/>
17: ....
18: </connectionStrings>
19: <system.web>
20: <compilation debug="true" targetFramework="4.0">
21:
22: </compilation>
23:
24: <authentication mode="Forms">
25: <forms loginUrl="~/Account/LogOn" timeout="2880" />
26: </authentication>
27:
28: <pages>
29: <namespaces>
30: <add namespace="System.Web.Helpers" />
31: <add namespace="System.Web.Mvc" />
32: <add namespace="System.Web.Mvc.Ajax" />
33: <add namespace="System.Web.Mvc.Html" />
34: <add namespace="System.Web.Routing" />
35: <add namespace="System.Web.WebPages"/>
36: </namespaces>
37: </pages>
38: </system.web>
39:
40: <system.webServer>
41: <validation validateIntegratedModeConfiguration="false"/>
42: <modules runAllManagedModulesForAllRequests="true"/>
43: </system.webServer>
44:
45: <runtime>
46: <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
47: <dependentAssembly>
48: <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
49: <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="3.0.0.0" />
50: </dependentAssembly>
51: </assemblyBinding>
52: </runtime>
53: </configuration>
Проєкт містить контроллер Login, який має вісімь сторінок. Ці сторінки гранично примітивні, але для подальшого порозуміння коду, імена їх полей потрібні.
Index.aspx
1: <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
2: <div style="margin-left:20px">
3:
4: <h2>Login & Register</h2>
5: <% Using Html.BeginForm("Index", "Login", Nothing, FormMethod.Post)%>
6:
7: <br /><br />
8: Email<br />
9: <input id="name" name="name" type="text" style="width:150px" /><br /><br />
10: Password<br />
11: <input id="pass" name="pass" type="password" style="width:150px" /><br /><br />
12: Remember Me<br />
13: <%: Html.CheckBox("remember")%> <br /><br />
14:
15: <input id="Submit1" type="submit" value="Login" style="width:150px" />
16:
17: <% End Using %>
18:
19: <br /><br />
20: <%: Html.ActionLink( "Register new user", "Register")%><br />
21: <%: Html.ActionLink( "Lost Password", "LostPassword")%>
22:
23: </div>
24: </asp:Content>
Logout.aspx
1: <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
2:
3: <h2>Logout</h2>
4: <br /><br />
5: <h3>Exit from site</h3>
6: <br /><br />
7: <% Using Html.BeginForm("Logout", "Cab")%>
8: <input id="Submit1" type="submit" value="Logout" style="width:150px" />
9: <% End Using%>
10:
11: </asp:Content>
LostPassword.aspx
1: <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
2:
3: <h2>Lost Password</h2>
4:
5: <h3>To restore password, please enter you Email</h3>
6:
7: <% Using Html.BeginForm("LostPassword", "Login")%>
8:
9: <br /><br />
10: you Email<br />
11:
12: <input id="email" name="email" type="text" style="width:150px" maxlength="20" placeholder="Enter you Email" required /><br /><br />
13: <br /><br />
14: <input id="Submit1" type="submit" value="OK" style="width:150px" />
15:
16: <% End Using %>
17:
18: </asp:Content>
Register.aspx
1: <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
2:
3: <h2>Register new user</h2>
4:
5: <% Using Html.BeginForm("Register", "Login") %>
6:
7: <br /><br />
8: First Name<br />
9: <input id="first" name="first" type="text" style="width:150px" maxlength="50" placeholder="Enter your First Name" required /><br /><br />
10: Last Name<br />
11: <input id="last" name="last" type="text" style="width:150px" maxlength="50" placeholder="Enter your Last Name" required /><br /><br />
12: Email<br />
13: <input id="email" name="email" type="email" style="width:150px" maxlength="50" placeholder="Enter your email" required /><br /><br />
14: Password<br />
15: <input id="pass" name="pass" type="password" style="width:150px" maxlength="20" placeholder="Enter your password" required /><br /><br />
16:
17: <input id="Submit1" type="submit" value="Register" style="width:150px" />
18:
19: <% End Using %>
20: <br />
21: <font color="red"><%: ViewData("Err1")%></font>
22:
23: </asp:Content>
ResetPassword.aspx
1: <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
2:
3: <h2><%: Model.FirstName%> <%: Model.LastName%>, please enter you new password:</h2>
4: <br /><br />
5:
6: <% Using Html.BeginForm("ResetPassword", "Login")%>
7:
8: <br /><br />
9: New Password<br />
10: <%: Html.Hidden("ID", RouteData.Values("ID").ToString)%>
11: <%: Html.Hidden("UID", Request.QueryString("UID"))%>
12: <%: Html.Hidden("Token", request.querystring("Token"))%>
13:
14: <input id="pass" name="pass" type="password" style="width:150px" maxlength="20" placeholder="Enter new password" required /><br /><br />
15: <br /><br />
16: <input id="Submit1" type="submit" value="Set" style="width:150px" />
17:
18: <% End Using %>
19:
20: </asp:Content>
ResetPasswordMSG.aspx
1: <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
2:
3: <h2><%: Model.FirstName%> <%: Model.LastName%>, please enter you new password:</h2>
4: <br /><br />
5:
6: <% Using Html.BeginForm("ResetPassword", "Login")%>
7:
8: <br /><br />
9: New Password<br />
10: <%: Html.Hidden("ID", RouteData.Values("ID").ToString)%>
11: <%: Html.Hidden("UID", Request.QueryString("UID"))%>
12: <%: Html.Hidden("Token", request.querystring("Token"))%>
13:
14: <input id="pass" name="pass" type="password" style="width:150px" maxlength="20" placeholder="Enter new password" required /><br /><br />
15: <br /><br />
16: <input id="Submit1" type="submit" value="Set" style="width:150px" />
17:
18: <% End Using %>
19:
20: </asp:Content>
RestorePass.aspx
1: <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
2:
3: <h2>Restore Password</h2>
4:
5: <h3>Restore my password through <%:Model.Email%> ?</h3>
6: <br /><br />
7: <% Using Html.BeginForm("RestorePass", "Login")%>
8: <%: Html.Hidden("Email", Model.ID)%>
9: <input id="Submit1" type="submit" value="Send" style="width:150px" />
10: <% End Using%>
11:
12: </asp:Content>
RestorePassMSG.aspx
1: <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
2:
3: <h3>Mail to <%:Model.Email%> sended</h3>
4: <br /><br />
5:
6: <% Using Html.BeginForm("Index", "Home", Nothing, FormMethod.Get)%>
7: <br /><br />
8: <input id="Submit1" type="submit" value="OK" style="width:150px" />
9: <% End Using%>
10:
11: </asp:Content>
Код контроллера нескладний, але містить усе, про було сказано вище. Тут є звернення до функції розсилки мила, яку я вже описував колись раніше - Поштові розсилки через Gmail. А також є редірект на сторінки кабінетів юзерів, залежно від прав юзерів (41-47).
Одноразовий токен утворюється у стрічках 197, 233, 249
1: Namespace OCMR
2: Public Class LoginController
3: Inherits System.Web.Mvc.Controller
4:
5:
6: Function Index() As ActionResult
7: Return View()
8: End Function
9:
10: <HttpPost()> _
11: Function Index(PRM As FormCollection) As ActionResult
12: Dim Name As String = PRM("name")
13: Dim Pass As String = PRM("pass")
14: Dim Remember As String = PRM("remember")
15: If Name Is Nothing Or Pass Is Nothing Or Remember Is Nothing Then
16: Return View("Index")
17: Exit Function
18: End If
19: If Name.Trim = "" Or Pass.Trim = "" Or Remember.Trim = "" Then
20: Return View("Index")
21: Exit Function
22: End If
23: Dim OCMR_DB As New OCMRDataContext
24: Dim UserID = (From X In OCMR_DB.Users Select X Where X.Email.Trim = Name.Trim And X.Password.Trim = Pass.Trim).ToList
25: If UserID.Count = 0 Then
26: Return View("Index")
27: Exit Function
28: End If
29: 'аутентификация
30: If Remember.Contains("true") Then
31: Dim ticket As New FormsAuthenticationTicket(1, UserID(0).Email, Now, Now.AddYears(1), True, "", FormsAuthentication.FormsCookiePath)
32: Dim EncryptTicket As String = FormsAuthentication.Encrypt(ticket)
33: Dim AUCook As HttpCookie = New HttpCookie(FormsAuthentication.FormsCookieName, EncryptTicket)
34: AUCook.Domain = System.Configuration.ConfigurationManager.AppSettings("LoginDomain")
35: AUCook.Expires = Now.AddYears(1)
36: Response.Cookies.Add(AUCook)
37: Else
38: FormsAuthentication.SetAuthCookie(UserID(0).Email, True)
39: End If
40: 'редирект в кабинет
41: If UserID(0).IsAdmin = 0 Then
42: Return RedirectToActionPermanent("Index", "Cab")
43: ElseIf UserID(0).IsAdmin = 1 Then
44: Return RedirectToActionPermanent("Index", "Admin")
45: ElseIf UserID(0).IsAdmin = 2 Then
46: Return RedirectToActionPermanent("Index", "SuperAdmin")
47: End If
48: End Function
49:
50: Function Register() As ActionResult
51: Return View()
52: End Function
53:
54: 'в єту форму просто так не зайти - чтобы не было атаки на сброс паролей, сначала надо записать в базу RestorePassID - это входной ID этой формы
55: Function RestorePass(ID As String) As ActionResult
56: Dim db1 As New OCMRDataContext
57: Dim AllUser = (From X In db1.Users Select X Where X.RestorePassID.ToString = ID).ToList
58: If AllUser.Count = 0 Then
59: Return RedirectToActionPermanent("Index", "Home")
60: Else
61: Return View("RestorePass", New With {.ID = ID, .Email = AllUser(0).Email})
62: End If
63: End Function
64:
65: 'ID - это RestorePassID в базе
66: <HttpPost()> _
67: Function RestorePass(PRM As FormCollection, ID As String) As ActionResult
68: Dim Email As String = PRM("Email").ToString
69: Dim db1 As New OCMRDataContext
70: Dim AllUser = (From X In db1.Users Select X Where X.RestorePassID.ToString = ID).ToList
71: If AllUser.Count = 0 Then
72: Return RedirectToActionPermanent("Index", "Home")
73: Else
74:
75: Dim HostingURL As String = "http://" & System.Configuration.ConfigurationManager.AppSettings("HostingURL")
76: Dim NameURL As String = System.Configuration.ConfigurationManager.AppSettings("NameURL")
77: Dim MD5String As String = MD5.Calculate(SecretConstant & AllUser(0).RestorePassID.ToString)
78: Dim SecureURL As String = HostingURL & "/Login/ResetPassword/" & ID & "?UID=" & AllUser(0).ID.ToString & "&Token=" & MD5String
79: Dim MailBody As String = _
80: "<html><body>Hello, " & AllUser(0).FirstName & " " & AllUser(0).LastName & "<br><br>" & _
81: "If you want to reset password in site " & "<a href=""" & HostingURL & """>" & NameURL & "</a>, <br><br>" & _
82: "please go to " & "<a href=""" & SecureURL & """>" & SecureURL & "</a>, <br><br>" & _
83: "Best regards, Administration of " & NameURL & " <br></body></html>"
84: GMail.SendMail(AllUser(0).Email, "Restore password", MailBody)
85: Return RedirectToAction("RestorePassMSG", New With {.id = ID})
86: End If
87: End Function
88:
89: Dim SecretConstant As String = System.Configuration.ConfigurationManager.AppSettings("SecretConstant")
90:
91: Function RestorePassMSG(ID As String) As ActionResult
92: Dim db1 As New OCMRDataContext
93: Dim AllUser = (From X In db1.Users Select X Where X.RestorePassID.ToString = ID).ToList
94: If AllUser.Count = 0 Then
95: Return RedirectToActionPermanent("Index", "Home")
96: Else
97: Return View("RestorePassMSG", New With {.ID = ID, .Email = AllUser(0).Email})
98: End If
99: End Function
100:
101: <HttpPost()> _
102: Function RestorePassMSG(PRM As FormCollection) As ActionResult
103: Return RedirectToActionPermanent("Index", "Home")
104: End Function
105:
106: 'Ссылка из мыла http://localhost:52796/Login/ResetPassword/a018a5b3-9f2d-43d7-bd84-9ebe48923eb4?UID=84193ab6-150e-4578-bc98-d77ea5375a31&Token=58E6D259DF79C68692D3128812C5E4A5,
107: Function ResetPassword(ID As String) As ActionResult
108: Dim UID As String = Request.QueryString("UID")
109: Dim Token As String = Request.QueryString("Token")
110: If ID Is Nothing Or UID Is Nothing Or Token Is Nothing Then
111: Return RedirectToActionPermanent("Index", "Home")
112: Exit Function
113: End If
114: If ID.Trim = "" Or UID.Trim = "" Or Token.Trim = "" Then
115: Return RedirectToActionPermanent("Index", "Home")
116: Exit Function
117: End If
118: If Len(Token) <> 32 Then
119: Return RedirectToActionPermanent("Index", "Home")
120: Exit Function
121: End If
122: Dim UID_ID As Guid
123: Dim ID_ID As Guid
124: Try
125: UID_ID = Guid.Parse(UID)
126: Catch ex As Exception
127: Return RedirectToActionPermanent("Index", "Home")
128: Exit Function
129: End Try
130: Try
131: ID_ID = Guid.Parse(ID)
132: Catch ex As Exception
133: Return RedirectToActionPermanent("Index", "Home")
134: Exit Function
135: End Try
136: Dim MD5String As String = MD5.Calculate(SecretConstant & ID)
137: If MD5String <> Token Then
138: Return RedirectToActionPermanent("Index", "Home")
139: Exit Function
140: End If
141: 'только теперь пошли в базу
142: Dim db1 As New OCMRDataContext
143: Dim AllUser = (From X In db1.Users Select X Where X.RestorePassID.ToString = ID And X.ID.ToString = UID).ToList
144: If AllUser.Count = 0 Then
145: Return RedirectToActionPermanent("Index", "Home")
146: Exit Function
147: End If
148: Return View(New With {.ID = ID, .FirstName = AllUser(0).FirstName, .LastName = AllUser(0).LastName, .UID = UID, .Token = Token})
149: End Function
150:
151: <HttpPost()> _
152: Function ResetPassword(PRM As FormCollection) As ActionResult
153: Dim ID As String = PRM("ID")
154: Dim UID As String = PRM("UID")
155: Dim Token As String = PRM("Token")
156: Dim NewPass As String = PRM("Pass")
157: If ID Is Nothing Or UID Is Nothing Or Token Is Nothing Or NewPass Is Nothing Then
158: Return RedirectToActionPermanent("Index", "Home")
159: Exit Function
160: End If
161: If ID.Trim = "" Or UID.Trim = "" Or Token.Trim = "" Or NewPass.Trim = "" Then
162: Return RedirectToActionPermanent("Index", "Home")
163: Exit Function
164: End If
165: If Len(Token) <> 32 Then
166: Return RedirectToActionPermanent("Index", "Home")
167: Exit Function
168: End If
169: Dim UID_ID As Guid
170: Dim ID_ID As Guid
171: Try
172: UID_ID = Guid.Parse(UID)
173: Catch ex As Exception
174: Return RedirectToActionPermanent("Index", "Home")
175: Exit Function
176: End Try
177: Try
178: ID_ID = Guid.Parse(ID)
179: Catch ex As Exception
180: Return RedirectToActionPermanent("Index", "Home")
181: Exit Function
182: End Try
183: Dim MD5String As String = MD5.Calculate(SecretConstant & ID)
184: If MD5String <> Token Then
185: Return RedirectToActionPermanent("Index", "Home")
186: Exit Function
187: End If
188: 'только теперь пошли в базу
189: Dim db1 As New OCMRDataContext
190: Dim AllUser = (From X In db1.Users Select X Where X.RestorePassID.ToString = ID And X.ID.ToString = UID).ToList
191: If AllUser.Count = 0 Then
192: Return RedirectToActionPermanent("Index", "Home")
193: Exit Function
194: End If
195: 'изменили пароль и сбросили токен для запрета повторного входа без нового мыла
196: AllUser(0).Password = NewPass
197: AllUser(0).RestorePassID = Guid.NewGuid
198: db1.SubmitChanges()
199: Return RedirectToActionPermanent("ResetPasswordMSG", New With {.id = AllUser(0).RestorePassID})
200: End Function
201:
202: Function ResetPasswordMSG(ID As String)
203: Dim db1 As New OCMRDataContext
204: Dim AllUser = (From X In db1.Users Select X Where X.RestorePassID.ToString = ID).ToList
205: If AllUser.Count = 0 Then
206: Return RedirectToActionPermanent("Index", "Home")
207: Exit Function
208: End If
209: Return View(New With {.FirstName = AllUser(0).FirstName, .LastName = AllUser(0).LastName})
210: End Function
211:
212: Function LostPassword() As ActionResult
213: Return View()
214: End Function
215:
216: <HttpPost()> _
217: Function LostPassword(PRM As FormCollection) As ActionResult
218: Dim Email As String = PRM("email")
219: If Email Is Nothing Then
220: Return RedirectToActionPermanent("Index", "Home")
221: Exit Function
222: End If
223: If Email.Trim = "" Then
224: Return RedirectToActionPermanent("Index", "Home")
225: Exit Function
226: End If
227: Dim db1 As New OCMRDataContext
228: Dim AllUser = (From X In db1.Users Select X Where X.Email = Email).ToList
229: If AllUser.Count = 0 Then
230: Return RedirectToActionPermanent("Index", "Home")
231: Exit Function
232: End If
233: AllUser(0).RestorePassID = Guid.NewGuid
234: db1.SubmitChanges()
235: Return RedirectToAction("RestorePass", New With {.ID = AllUser(0).RestorePassID})
236: End Function
237:
238: <HttpPost()> _
239: Function Register(PRM As FormCollection) As ActionResult
240: Try
241: Dim Email As String = PRM("email")
242: Dim First As String = PRM("first")
243: Dim Last As String = PRM("last")
244: Dim Pass As String = PRM("pass")
245: '
246: Dim db1 As New OCMRDataContext
247: Dim AllUser = (From X In db1.Users Select X Where X.Email.Trim = Email.Trim).ToList
248: If AllUser.Count > 0 Then
249: AllUser(0).RestorePassID = Guid.NewGuid
250: db1.SubmitChanges()
251: Return RedirectToAction("RestorePass", New With {.ID = AllUser(0).RestorePassID})
252: End If
253: '
254: Dim NewUser = New User With { _
255: .ID = Guid.NewGuid, _
256: .FirstName = First, _
257: .LastName = Last, _
258: .Email = Email, _
259: .Password = Pass, _
260: .CrDate = Now, _
261: .LastLoginDate = Now, _
262: .DailyMail = 0, _
263: .WeeklyMail = 0, _
264: .IsAdmin = 0}
265: db1.Users.InsertOnSubmit(NewUser)
266: db1.SubmitChanges()
267:
268: ' FormsAuthentication.SetAuthCookie(Prm("txLogin"), True) - или просто
269: Dim ticket As New FormsAuthenticationTicket(1, Email, Now, Now.AddYears(1), True, "", FormsAuthentication.FormsCookiePath)
270: Dim EncryptTicket As String = FormsAuthentication.Encrypt(ticket)
271: Dim AUCook As HttpCookie = New HttpCookie(FormsAuthentication.FormsCookieName, EncryptTicket)
272: AUCook.Domain = System.Configuration.ConfigurationManager.AppSettings("LoginDomain")
273: Response.Cookies.Add(AUCook)
274: Return RedirectToAction("Index", "Cab")
275: Catch ex As Exception
276: ViewData("Err1") = ex.Message
277: Return View()
278: End Try
279: End Function
280:
281: Function Logout() As ActionResult
282: If Not User.Identity.IsAuthenticated Then Return RedirectToActionPermanent("Index", "Home")
283: Return View()
284: End Function
285:
286: <HttpPost()> _
287: Function Logout(Prm As FormCollection) As ActionResult
288: FormsAuthentication.SignOut()
289: Return RedirectToActionPermanent("Index", "Home")
290: End Function
291:
292: End Class
293:
294: End Namespace
Функція MD5, яку я добавив до своєї старої логики, щоб зв'язати одноразовий тікет з ID юзера та параметром SecretConstant у конфигі, виглядає ось так:
1: Public Class MD5
2:
3: Public Shared Function Calculate(Str1 As String) As String
4: 'step 1, calculate MD5 hash from input
5: Dim md5 As System.Security.Cryptography.MD5 = System.Security.Cryptography.MD5.Create()
6: Dim inputBytes As Byte() = System.Text.Encoding.Unicode.GetBytes(Str1)
7: Dim hash As Byte() = md5.ComputeHash(inputBytes)
8: 'step 2, convert byte array to hex string
9: Dim sb As StringBuilder = New StringBuilder
10: Dim I As Integer
11: While I < hash.Length
12: sb.Append(hash(I).ToString("X2"))
13: I += 1
14: End While
15: Return sb.ToString()
16: End Function
17:
18: End Class
У стрічці 77 контроллеру ця функція первинно утворює параметр Token для URL, а потім (у стрічках 137 і 184 контроллеру) вона порівнюється з тим, що приходить в URL від юзера.
Зверніть увагу, що у цьому варіанті мого кода формується токен, який не мае терміну дійсності. Для формування токена, який з часом застаріває треба у цю функцію ввести поточний час.