mirror of
https://gitlab.com/buildfeed/BuildFeed.git
synced 2024-03-22 21:10:34 +08:00
User registration now actually works
Also package updates
This commit is contained in:
parent
74cbe3cb22
commit
80642a2402
133
Authentication/MongoAuth/Base32Encoding.cs
Normal file
133
Authentication/MongoAuth/Base32Encoding.cs
Normal file
|
@ -0,0 +1,133 @@
|
|||
using System;
|
||||
|
||||
namespace MongoAuth
|
||||
{
|
||||
/* Based off http://stackoverflow.com/a/7135008 */
|
||||
internal static class Base32Encoding
|
||||
{
|
||||
public static byte[] ToBytes(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
}
|
||||
|
||||
input = input.TrimEnd('='); //remove padding characters
|
||||
int byteCount = input.Length * 5 / 8; //this must be TRUNCATED
|
||||
var returnArray = new byte[byteCount];
|
||||
|
||||
byte curByte = 0,
|
||||
bitsRemaining = 8;
|
||||
|
||||
int arrayIndex = 0;
|
||||
|
||||
foreach (char c in input)
|
||||
{
|
||||
int cValue = CharToValue(c);
|
||||
|
||||
int mask;
|
||||
if (bitsRemaining > 5)
|
||||
{
|
||||
mask = cValue << (bitsRemaining - 5);
|
||||
curByte = (byte)(curByte | mask);
|
||||
bitsRemaining -= 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
mask = cValue >> (5 - bitsRemaining);
|
||||
curByte = (byte)(curByte | mask);
|
||||
returnArray[arrayIndex++] = curByte;
|
||||
curByte = (byte)(cValue << (3 + bitsRemaining));
|
||||
bitsRemaining += 3;
|
||||
}
|
||||
}
|
||||
|
||||
//if we didn't end with a full byte
|
||||
if (arrayIndex != byteCount)
|
||||
{
|
||||
returnArray[arrayIndex] = curByte;
|
||||
}
|
||||
|
||||
return returnArray;
|
||||
}
|
||||
|
||||
public static string ToString(byte[] input)
|
||||
{
|
||||
if (input == null || input.Length == 0)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
}
|
||||
|
||||
int charCount = (int)Math.Ceiling(input.Length / 5d) * 8;
|
||||
var returnArray = new char[charCount];
|
||||
|
||||
byte nextChar = 0,
|
||||
bitsRemaining = 5;
|
||||
int arrayIndex = 0;
|
||||
|
||||
foreach (byte b in input)
|
||||
{
|
||||
nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining)));
|
||||
returnArray[arrayIndex++] = ValueToChar(nextChar);
|
||||
|
||||
if (bitsRemaining < 4)
|
||||
{
|
||||
nextChar = (byte)((b >> (3 - bitsRemaining)) & 31);
|
||||
returnArray[arrayIndex++] = ValueToChar(nextChar);
|
||||
bitsRemaining += 5;
|
||||
}
|
||||
|
||||
bitsRemaining -= 3;
|
||||
nextChar = (byte)((b << bitsRemaining) & 31);
|
||||
}
|
||||
|
||||
//if we didn't end with a full char
|
||||
if (arrayIndex != charCount)
|
||||
{
|
||||
returnArray[arrayIndex++] = ValueToChar(nextChar);
|
||||
while (arrayIndex != charCount)
|
||||
returnArray[arrayIndex++] = '='; //padding
|
||||
}
|
||||
|
||||
return new string(returnArray);
|
||||
}
|
||||
|
||||
private static int CharToValue(char c)
|
||||
{
|
||||
int value = c;
|
||||
|
||||
//65-90 == uppercase letters
|
||||
if (value < 91 && value > 64)
|
||||
{
|
||||
return value - 65;
|
||||
}
|
||||
//50-55 == numbers 2-7
|
||||
if (value < 56 && value > 49)
|
||||
{
|
||||
return value - 24;
|
||||
}
|
||||
//97-122 == lowercase letters
|
||||
if (value < 123 && value > 96)
|
||||
{
|
||||
return value - 97;
|
||||
}
|
||||
|
||||
throw new ArgumentException("Character is not a Base32 character.", nameof(c));
|
||||
}
|
||||
|
||||
private static char ValueToChar(byte b)
|
||||
{
|
||||
if (b < 26)
|
||||
{
|
||||
return (char)(b + 65);
|
||||
}
|
||||
|
||||
if (b < 32)
|
||||
{
|
||||
return (char)(b + 24);
|
||||
}
|
||||
|
||||
throw new ArgumentException("Byte is not a value Base32 value.", nameof(b));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,34 +2,34 @@
|
|||
|
||||
namespace MongoAuth
|
||||
{
|
||||
internal static class DatabaseConfig
|
||||
{
|
||||
public static string Host { get; }
|
||||
public static int Port { get; }
|
||||
public static string Database { get; }
|
||||
public static string Username { get; }
|
||||
public static string Password { get; }
|
||||
internal static class DatabaseConfig
|
||||
{
|
||||
public static string Host { get; }
|
||||
public static int Port { get; }
|
||||
public static string Database { get; }
|
||||
public static string Username { get; }
|
||||
public static string Password { get; }
|
||||
|
||||
static DatabaseConfig()
|
||||
{
|
||||
Host = !string.IsNullOrEmpty(ConfigurationManager.AppSettings["data:MongoHost"])
|
||||
? ConfigurationManager.AppSettings["data:MongoHost"]
|
||||
: "localhost";
|
||||
static DatabaseConfig()
|
||||
{
|
||||
Host = !string.IsNullOrEmpty(ConfigurationManager.AppSettings["data:MongoHost"])
|
||||
? ConfigurationManager.AppSettings["data:MongoHost"]
|
||||
: "localhost";
|
||||
|
||||
int _port;
|
||||
bool success = int.TryParse(ConfigurationManager.AppSettings["data:MongoPort"], out _port);
|
||||
if (!success)
|
||||
{
|
||||
_port = 27017; // mongo default port
|
||||
}
|
||||
Port = _port;
|
||||
int port;
|
||||
bool success = int.TryParse(ConfigurationManager.AppSettings["data:MongoPort"], out port);
|
||||
if (!success)
|
||||
{
|
||||
port = 27017; // mongo default port
|
||||
}
|
||||
Port = port;
|
||||
|
||||
Database = !string.IsNullOrEmpty(ConfigurationManager.AppSettings["data:MongoDB"])
|
||||
? ConfigurationManager.AppSettings["data:MongoDB"]
|
||||
: "MongoAuth";
|
||||
Database = !string.IsNullOrEmpty(ConfigurationManager.AppSettings["data:MongoDB"])
|
||||
? ConfigurationManager.AppSettings["data:MongoDB"]
|
||||
: "MongoAuth";
|
||||
|
||||
Username = ConfigurationManager.AppSettings["data:MongoUser"] ?? "";
|
||||
Password = ConfigurationManager.AppSettings["data:MongoPass"] ?? "";
|
||||
}
|
||||
}
|
||||
Username = ConfigurationManager.AppSettings["data:MongoUser"] ?? "";
|
||||
Password = ConfigurationManager.AppSettings["data:MongoPass"] ?? "";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -56,6 +56,7 @@
|
|||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Base32Encoding.cs" />
|
||||
<Compile Include="DatabaseConfig.cs" />
|
||||
<Compile Include="MongoMembershipProvider.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Configuration;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
@ -12,419 +13,468 @@
|
|||
|
||||
namespace MongoAuth
|
||||
{
|
||||
public class MongoMembershipProvider : MembershipProvider
|
||||
{
|
||||
private const string MEMBER_COLLECTION_NAME = "members";
|
||||
public class MongoMembershipProvider : MembershipProvider
|
||||
{
|
||||
private const string MEMBER_COLLECTION_NAME = "members";
|
||||
|
||||
private bool _enablePasswordReset = true;
|
||||
private int _maxInvalidPasswordAttempts = 5;
|
||||
private bool _enablePasswordReset = true;
|
||||
private int _maxInvalidPasswordAttempts = 5;
|
||||
|
||||
private IMongoCollection<MongoMember> _memberCollection;
|
||||
private int _minRequiredNonAlphanumericCharacters = 1;
|
||||
private int _minRequriedPasswordLength = 8;
|
||||
private int _passwordAttemptWindow = 60;
|
||||
private bool _requiresUniqueEmail = true;
|
||||
private IMongoCollection<MongoMember> _memberCollection;
|
||||
private int _minRequiredNonAlphanumericCharacters = 1;
|
||||
private int _minRequriedPasswordLength = 8;
|
||||
private int _passwordAttemptWindow = 60;
|
||||
private bool _requiresUniqueEmail = true;
|
||||
|
||||
public override string ApplicationName { get; set; }
|
||||
public override string ApplicationName { get; set; }
|
||||
|
||||
public override bool EnablePasswordReset => _enablePasswordReset;
|
||||
public override bool EnablePasswordReset => _enablePasswordReset;
|
||||
|
||||
public override bool EnablePasswordRetrieval => false;
|
||||
public override bool EnablePasswordRetrieval => false;
|
||||
|
||||
public override int MaxInvalidPasswordAttempts => _maxInvalidPasswordAttempts;
|
||||
public override int MaxInvalidPasswordAttempts => _maxInvalidPasswordAttempts;
|
||||
|
||||
public override int MinRequiredNonAlphanumericCharacters => _minRequiredNonAlphanumericCharacters;
|
||||
public override int MinRequiredNonAlphanumericCharacters => _minRequiredNonAlphanumericCharacters;
|
||||
|
||||
public override int MinRequiredPasswordLength => _minRequriedPasswordLength;
|
||||
public override int MinRequiredPasswordLength => _minRequriedPasswordLength;
|
||||
|
||||
public override int PasswordAttemptWindow => _passwordAttemptWindow;
|
||||
public override int PasswordAttemptWindow => _passwordAttemptWindow;
|
||||
|
||||
public override MembershipPasswordFormat PasswordFormat => MembershipPasswordFormat.Hashed;
|
||||
public override MembershipPasswordFormat PasswordFormat => MembershipPasswordFormat.Hashed;
|
||||
|
||||
public override string PasswordStrengthRegularExpression => "";
|
||||
public override string PasswordStrengthRegularExpression => "";
|
||||
|
||||
public override bool RequiresQuestionAndAnswer => false;
|
||||
public override bool RequiresQuestionAndAnswer => false;
|
||||
|
||||
public override bool RequiresUniqueEmail => _requiresUniqueEmail;
|
||||
public override bool RequiresUniqueEmail => _requiresUniqueEmail;
|
||||
|
||||
public override void Initialize(string name, NameValueCollection config)
|
||||
{
|
||||
if (config == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(config));
|
||||
}
|
||||
|
||||
base.Initialize(name, config);
|
||||
|
||||
_enablePasswordReset = TryReadBool(config["enablePasswordReset"], _enablePasswordReset);
|
||||
_maxInvalidPasswordAttempts = TryReadInt(config["maxInvalidPasswordAttempts"], _maxInvalidPasswordAttempts);
|
||||
_minRequiredNonAlphanumericCharacters = TryReadInt(config["minRequiredNonAlphanumericCharacters"], _minRequiredNonAlphanumericCharacters);
|
||||
_minRequriedPasswordLength = TryReadInt(config["minRequriedPasswordLength"], _minRequriedPasswordLength);
|
||||
_passwordAttemptWindow = TryReadInt(config["passwordAttemptWindow"], _passwordAttemptWindow);
|
||||
_requiresUniqueEmail = TryReadBool(config["requiresUniqueEmail"], _requiresUniqueEmail);
|
||||
|
||||
|
||||
MongoClientSettings settings = new MongoClientSettings
|
||||
{
|
||||
Server = new MongoServerAddress(DatabaseConfig.Host, DatabaseConfig.Port)
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(DatabaseConfig.Username) && !string.IsNullOrEmpty(DatabaseConfig.Password))
|
||||
{
|
||||
settings.Credentials = new List<MongoCredential>
|
||||
public override void Initialize(string name, NameValueCollection config)
|
||||
{
|
||||
if (config == null)
|
||||
{
|
||||
MongoCredential.CreateCredential(DatabaseConfig.Database, DatabaseConfig.Username, DatabaseConfig.Password)
|
||||
throw new ArgumentNullException(nameof(config));
|
||||
}
|
||||
|
||||
base.Initialize(name, config);
|
||||
|
||||
_enablePasswordReset = TryReadBool(config["enablePasswordReset"], _enablePasswordReset);
|
||||
_maxInvalidPasswordAttempts = TryReadInt(config["maxInvalidPasswordAttempts"], _maxInvalidPasswordAttempts);
|
||||
_minRequiredNonAlphanumericCharacters = TryReadInt(config["minRequiredNonAlphanumericCharacters"], _minRequiredNonAlphanumericCharacters);
|
||||
_minRequriedPasswordLength = TryReadInt(config["minRequriedPasswordLength"], _minRequriedPasswordLength);
|
||||
_passwordAttemptWindow = TryReadInt(config["passwordAttemptWindow"], _passwordAttemptWindow);
|
||||
_requiresUniqueEmail = TryReadBool(config["requiresUniqueEmail"], _requiresUniqueEmail);
|
||||
|
||||
|
||||
MongoClientSettings settings = new MongoClientSettings
|
||||
{
|
||||
Server = new MongoServerAddress(DatabaseConfig.Host, DatabaseConfig.Port)
|
||||
};
|
||||
}
|
||||
|
||||
MongoClient dbClient = new MongoClient(settings);
|
||||
if (!string.IsNullOrEmpty(DatabaseConfig.Username) && !string.IsNullOrEmpty(DatabaseConfig.Password))
|
||||
{
|
||||
settings.Credentials = new List<MongoCredential>
|
||||
{
|
||||
MongoCredential.CreateCredential(DatabaseConfig.Database, DatabaseConfig.Username, DatabaseConfig.Password)
|
||||
};
|
||||
}
|
||||
|
||||
_memberCollection = dbClient.GetDatabase(DatabaseConfig.Database).GetCollection<MongoMember>(MEMBER_COLLECTION_NAME);
|
||||
}
|
||||
MongoClient dbClient = new MongoClient(settings);
|
||||
|
||||
public override bool ChangePassword(string username, string oldPassword, string newPassword)
|
||||
{
|
||||
bool isAuthenticated = ValidateUser(username, oldPassword);
|
||||
_memberCollection = dbClient.GetDatabase(DatabaseConfig.Database).GetCollection<MongoMember>(MEMBER_COLLECTION_NAME);
|
||||
}
|
||||
|
||||
if (isAuthenticated)
|
||||
{
|
||||
Task<MongoMember> task = _memberCollection.Find(m => m.UserName.ToLower() == username.ToLower()).SingleOrDefaultAsync();
|
||||
public override bool ChangePassword(string username, string oldPassword, string newPassword)
|
||||
{
|
||||
bool isAuthenticated = ValidateUser(username, oldPassword);
|
||||
|
||||
if (isAuthenticated)
|
||||
{
|
||||
Task<MongoMember> task = _memberCollection.Find(m => m.UserName.ToLower() == username.ToLower()).SingleOrDefaultAsync();
|
||||
task.Wait();
|
||||
MongoMember mm = task.Result;
|
||||
|
||||
if (mm == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var salt = new byte[24];
|
||||
byte[] hash = CalculateHash(newPassword, ref salt);
|
||||
|
||||
mm.PassSalt = salt;
|
||||
mm.PassHash = hash;
|
||||
|
||||
Task<ReplaceOneResult> replaceTask = _memberCollection.ReplaceOneAsync(m => m.Id == mm.Id, mm);
|
||||
replaceTask.Wait();
|
||||
|
||||
return replaceTask.IsCompleted;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
|
||||
{
|
||||
if (password.Length < MinRequiredPasswordLength)
|
||||
{
|
||||
status = MembershipCreateStatus.InvalidPassword;
|
||||
return null;
|
||||
}
|
||||
|
||||
MembershipUser mu = null;
|
||||
|
||||
Task<long> dupeUsers = _memberCollection.Find(m => m.UserName.ToLower() == username.ToLower()).CountAsync();
|
||||
Task<long> dupeEmails = _memberCollection.Find(m => m.EmailAddress.ToLower() == email.ToLower()).CountAsync();
|
||||
dupeUsers.Wait();
|
||||
dupeEmails.Wait();
|
||||
|
||||
if (dupeUsers.Result > 0)
|
||||
{
|
||||
status = MembershipCreateStatus.DuplicateUserName;
|
||||
}
|
||||
else if (dupeEmails.Result > 0)
|
||||
{
|
||||
status = MembershipCreateStatus.DuplicateEmail;
|
||||
}
|
||||
else
|
||||
{
|
||||
var salt = new byte[24];
|
||||
byte[] hash = CalculateHash(password, ref salt);
|
||||
|
||||
MongoMember mm = new MongoMember
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserName = username,
|
||||
PassHash = hash,
|
||||
PassSalt = salt,
|
||||
EmailAddress = email,
|
||||
IsApproved = isApproved,
|
||||
IsLockedOut = false,
|
||||
CreationDate = DateTime.UtcNow,
|
||||
LastLoginDate = DateTime.MinValue,
|
||||
LastActivityDate = DateTime.MinValue,
|
||||
LastLockoutDate = DateTime.MinValue
|
||||
};
|
||||
|
||||
Task insertTask = _memberCollection.InsertOneAsync(mm);
|
||||
insertTask.Wait();
|
||||
|
||||
if (insertTask.Status == TaskStatus.RanToCompletion)
|
||||
{
|
||||
status = MembershipCreateStatus.Success;
|
||||
mu = new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, mm.CreationDate, mm.LastLoginDate, mm.LastActivityDate, DateTime.MinValue, mm.LastLockoutDate);
|
||||
}
|
||||
else
|
||||
{
|
||||
status = MembershipCreateStatus.ProviderError;
|
||||
}
|
||||
}
|
||||
|
||||
return mu;
|
||||
}
|
||||
|
||||
public override bool DeleteUser(string username, bool deleteAllRelatedData)
|
||||
{
|
||||
Task<DeleteResult> task = _memberCollection.DeleteOneAsync(m => m.UserName.ToLower() == username.ToLower());
|
||||
task.Wait();
|
||||
|
||||
return task.Result.IsAcknowledged && task.Result.DeletedCount == 1;
|
||||
}
|
||||
|
||||
public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
|
||||
{
|
||||
MembershipUserCollection muc = new MembershipUserCollection();
|
||||
|
||||
IFindFluent<MongoMember, MongoMember> users = _memberCollection.Find(new BsonDocument());
|
||||
|
||||
Task<long> totalRecordsTask = users.CountAsync();
|
||||
totalRecordsTask.Wait();
|
||||
totalRecords = Convert.ToInt32(totalRecordsTask.Result);
|
||||
|
||||
users = users.Skip(pageIndex * pageSize).Limit(pageSize);
|
||||
Task<List<MongoMember>> pageItemsTask = users.ToListAsync();
|
||||
pageItemsTask.Wait();
|
||||
|
||||
foreach (MongoMember mm in pageItemsTask.Result)
|
||||
{
|
||||
muc.Add(new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, FixupDatesFromMongo(mm.CreationDate), FixupDatesFromMongo(mm.LastLoginDate), FixupDatesFromMongo(mm.LastActivityDate), DateTime.MinValue, FixupDatesFromMongo(mm.LastLockoutDate)));
|
||||
}
|
||||
|
||||
return muc;
|
||||
}
|
||||
|
||||
public override int GetNumberOfUsersOnline()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override string GetPassword(string username, string answer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override MembershipUser GetUser(string username, bool userIsOnline)
|
||||
{
|
||||
Task<MongoMember> task = _memberCollection.Find(f => f.UserName.ToLower() == username.ToLower()).FirstOrDefaultAsync();
|
||||
task.Wait();
|
||||
|
||||
MongoMember mm = task.Result;
|
||||
|
||||
return mm == null
|
||||
? null
|
||||
: new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, FixupDatesFromMongo(mm.CreationDate), FixupDatesFromMongo(mm.LastLoginDate), FixupDatesFromMongo(mm.LastActivityDate), DateTime.MinValue, FixupDatesFromMongo(mm.LastLockoutDate));
|
||||
}
|
||||
|
||||
public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
|
||||
{
|
||||
Task<MongoMember> task = _memberCollection.Find(f => f.Id == (Guid)providerUserKey).FirstOrDefaultAsync();
|
||||
task.Wait();
|
||||
|
||||
MongoMember mm = task.Result;
|
||||
|
||||
return mm == null
|
||||
? null
|
||||
: new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, FixupDatesFromMongo(mm.CreationDate), FixupDatesFromMongo(mm.LastLoginDate), FixupDatesFromMongo(mm.LastActivityDate), DateTime.MinValue, FixupDatesFromMongo(mm.LastLockoutDate));
|
||||
}
|
||||
|
||||
public override string GetUserNameByEmail(string email)
|
||||
{
|
||||
Task<MongoMember> task = _memberCollection.Find(f => f.EmailAddress.ToLower() == email.ToLower()).FirstOrDefaultAsync();
|
||||
task.Wait();
|
||||
|
||||
return task.Result.UserName;
|
||||
}
|
||||
|
||||
public override string ResetPassword(string username, string answer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void ChangeApproval(Guid id, bool newStatus)
|
||||
{
|
||||
Task<UpdateResult> task = _memberCollection.UpdateOneAsync(Builders<MongoMember>.Filter.Eq(u => u.Id, id), Builders<MongoMember>.Update.Set(u => u.IsApproved, newStatus));
|
||||
task.Wait();
|
||||
}
|
||||
|
||||
public void ChangeLockStatus(Guid id, bool newStatus)
|
||||
{
|
||||
var updateDefinition = new List<UpdateDefinition<MongoMember>>
|
||||
{
|
||||
Builders<MongoMember>.Update.Set(u => u.IsLockedOut, newStatus)
|
||||
};
|
||||
|
||||
if (newStatus)
|
||||
{
|
||||
updateDefinition.Add(Builders<MongoMember>.Update.Set(u => u.LastLockoutDate, DateTime.UtcNow));
|
||||
}
|
||||
else
|
||||
{
|
||||
updateDefinition.Add(Builders<MongoMember>.Update.Set(u => u.LockoutWindowAttempts, 0));
|
||||
updateDefinition.Add(Builders<MongoMember>.Update.Set(u => u.LastLockoutDate, DateTime.MinValue));
|
||||
}
|
||||
|
||||
Task<UpdateResult> task = _memberCollection.UpdateOneAsync(Builders<MongoMember>.Filter.Eq(u => u.Id, id), Builders<MongoMember>.Update.Combine(updateDefinition));
|
||||
task.Wait();
|
||||
}
|
||||
|
||||
public override bool UnlockUser(string userName)
|
||||
{
|
||||
Task<UpdateResult> task = _memberCollection.UpdateOneAsync(Builders<MongoMember>.Filter.Eq(m => m.UserName.ToLower(), userName.ToLower()), Builders<MongoMember>.Update.Set(m => m.IsLockedOut, false));
|
||||
task.Wait();
|
||||
|
||||
return task.Result.IsAcknowledged && task.Result.ModifiedCount == 1;
|
||||
}
|
||||
|
||||
public override void UpdateUser(MembershipUser user)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override bool ValidateUser(string username, string password)
|
||||
{
|
||||
Task<MongoMember> task = _memberCollection.Find(f => f.UserName.ToLower() == username.ToLower()).FirstOrDefaultAsync();
|
||||
task.Wait();
|
||||
MongoMember mm = task.Result;
|
||||
|
||||
if (mm == null)
|
||||
if (mm == null
|
||||
|| !(mm.IsApproved && !mm.IsLockedOut))
|
||||
{
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
var salt = new byte[24];
|
||||
byte[] hash = CalculateHash(newPassword, ref salt);
|
||||
|
||||
mm.PassSalt = salt;
|
||||
mm.PassHash = hash;
|
||||
|
||||
Task<ReplaceOneResult> replaceTask = _memberCollection.ReplaceOneAsync(m => m.Id == mm.Id, mm);
|
||||
replaceTask.Wait();
|
||||
|
||||
return replaceTask.IsCompleted;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override MembershipUser CreateUser(string username, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
|
||||
{
|
||||
if (password.Length < MinRequiredPasswordLength)
|
||||
{
|
||||
status = MembershipCreateStatus.InvalidPassword;
|
||||
return null;
|
||||
}
|
||||
|
||||
MembershipUser mu = null;
|
||||
|
||||
Task<long> dupeUsers = _memberCollection.Find(m => m.UserName.ToLower() == username.ToLower()).CountAsync();
|
||||
Task<long> dupeEmails = _memberCollection.Find(m => m.EmailAddress.ToLower() == email.ToLower()).CountAsync();
|
||||
dupeUsers.Wait();
|
||||
dupeEmails.Wait();
|
||||
|
||||
if (dupeUsers.Result > 0)
|
||||
{
|
||||
status = MembershipCreateStatus.DuplicateUserName;
|
||||
}
|
||||
else if (dupeEmails.Result > 0)
|
||||
{
|
||||
status = MembershipCreateStatus.DuplicateEmail;
|
||||
}
|
||||
else
|
||||
{
|
||||
var salt = new byte[24];
|
||||
byte[] salt = mm.PassSalt;
|
||||
byte[] hash = CalculateHash(password, ref salt);
|
||||
|
||||
MongoMember mm = new MongoMember
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserName = username,
|
||||
PassHash = hash,
|
||||
PassSalt = salt,
|
||||
EmailAddress = email,
|
||||
IsApproved = false,
|
||||
IsLockedOut = false,
|
||||
CreationDate = DateTime.Now,
|
||||
LastLoginDate = DateTime.MinValue,
|
||||
LastActivityDate = DateTime.MinValue,
|
||||
LastLockoutDate = DateTime.MinValue
|
||||
};
|
||||
bool isFail = false;
|
||||
|
||||
Task insertTask = _memberCollection.InsertOneAsync(mm);
|
||||
insertTask.Wait();
|
||||
|
||||
if (insertTask.Status == TaskStatus.RanToCompletion)
|
||||
for (int i = 0; i > hash.Length; i++)
|
||||
{
|
||||
status = MembershipCreateStatus.Success;
|
||||
mu = new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, mm.CreationDate, mm.LastLoginDate, mm.LastActivityDate, DateTime.MinValue, mm.LastLockoutDate);
|
||||
isFail |= hash[i] != mm.PassHash[i];
|
||||
}
|
||||
|
||||
|
||||
if (isFail)
|
||||
{
|
||||
if (mm.LockoutWindowStart == DateTime.MinValue)
|
||||
{
|
||||
mm.LockoutWindowStart = DateTime.UtcNow;
|
||||
mm.LockoutWindowAttempts = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mm.LockoutWindowStart.AddMinutes(PasswordAttemptWindow) > DateTime.UtcNow)
|
||||
{
|
||||
// still within window
|
||||
mm.LockoutWindowAttempts++;
|
||||
if (mm.LockoutWindowAttempts >= MaxInvalidPasswordAttempts)
|
||||
{
|
||||
mm.IsLockedOut = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// outside of window, reset
|
||||
mm.LockoutWindowStart = DateTime.UtcNow;
|
||||
mm.LockoutWindowAttempts = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
status = MembershipCreateStatus.ProviderError;
|
||||
mm.LastLoginDate = DateTime.UtcNow;
|
||||
mm.LockoutWindowStart = DateTime.MinValue;
|
||||
mm.LockoutWindowAttempts = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return mu;
|
||||
}
|
||||
Task<ReplaceOneResult> updTask = _memberCollection.ReplaceOneAsync(Builders<MongoMember>.Filter.Eq(u => u.Id, mm.Id), mm);
|
||||
updTask.Wait();
|
||||
|
||||
public override bool DeleteUser(string username, bool deleteAllRelatedData)
|
||||
{
|
||||
Task<DeleteResult> task = _memberCollection.DeleteOneAsync(m => m.UserName.ToLower() == username.ToLower());
|
||||
task.Wait();
|
||||
return !isFail;
|
||||
}
|
||||
|
||||
return task.Result.IsAcknowledged && (task.Result.DeletedCount == 1);
|
||||
}
|
||||
|
||||
public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
|
||||
{
|
||||
MembershipUserCollection muc = new MembershipUserCollection();
|
||||
|
||||
IFindFluent<MongoMember, MongoMember> users = _memberCollection.Find(new BsonDocument());
|
||||
|
||||
Task<long> totalRecordsTask = users.CountAsync();
|
||||
totalRecordsTask.Wait();
|
||||
totalRecords = Convert.ToInt32(totalRecordsTask.Result);
|
||||
|
||||
users = users.Skip(pageIndex * pageSize).Limit(pageSize);
|
||||
Task<List<MongoMember>> pageItemsTask = users.ToListAsync();
|
||||
pageItemsTask.Wait();
|
||||
|
||||
foreach (MongoMember mm in pageItemsTask.Result)
|
||||
{
|
||||
muc.Add(new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, mm.CreationDate, mm.LastLoginDate, mm.LastActivityDate, DateTime.MinValue, mm.LastLockoutDate));
|
||||
}
|
||||
|
||||
return muc;
|
||||
}
|
||||
|
||||
public override int GetNumberOfUsersOnline()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override string GetPassword(string username, string answer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override MembershipUser GetUser(string username, bool userIsOnline)
|
||||
{
|
||||
Task<MongoMember> task = _memberCollection.Find(f => f.UserName.ToLower() == username.ToLower()).FirstOrDefaultAsync();
|
||||
task.Wait();
|
||||
|
||||
MongoMember mm = task.Result;
|
||||
|
||||
return mm == null
|
||||
? null
|
||||
: new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, mm.CreationDate, mm.LastLoginDate, mm.LastActivityDate, DateTime.MinValue, mm.LastLockoutDate);
|
||||
}
|
||||
|
||||
public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
|
||||
{
|
||||
Task<MongoMember> task = _memberCollection.Find(f => f.Id == (Guid)providerUserKey).FirstOrDefaultAsync();
|
||||
task.Wait();
|
||||
|
||||
MongoMember mm = task.Result;
|
||||
|
||||
return mm == null
|
||||
? null
|
||||
: new MembershipUser(Name, mm.UserName, mm.Id, mm.EmailAddress, "", "", mm.IsApproved, mm.IsLockedOut, mm.CreationDate, mm.LastLoginDate, mm.LastActivityDate, DateTime.MinValue, mm.LastLockoutDate);
|
||||
}
|
||||
|
||||
public override string GetUserNameByEmail(string email)
|
||||
{
|
||||
Task<MongoMember> task = _memberCollection.Find(f => f.EmailAddress.ToLower() == email.ToLower()).FirstOrDefaultAsync();
|
||||
task.Wait();
|
||||
|
||||
return task.Result.UserName;
|
||||
}
|
||||
|
||||
public override string ResetPassword(string username, string answer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void ChangeApproval(Guid id, bool newStatus)
|
||||
{
|
||||
Task<UpdateResult> task = _memberCollection.UpdateOneAsync(Builders<MongoMember>.Filter.Eq(u => u.Id, id), Builders<MongoMember>.Update.Set(u => u.IsApproved, newStatus));
|
||||
task.Wait();
|
||||
}
|
||||
|
||||
public void ChangeLockStatus(Guid id, bool newStatus)
|
||||
{
|
||||
var updateDefinition = new List<UpdateDefinition<MongoMember>>
|
||||
{
|
||||
Builders<MongoMember>.Update.Set(u => u.IsLockedOut, newStatus)
|
||||
};
|
||||
|
||||
if (newStatus)
|
||||
{
|
||||
updateDefinition.Add(Builders<MongoMember>.Update.Set(u => u.LastLockoutDate, DateTime.Now));
|
||||
}
|
||||
else
|
||||
{
|
||||
updateDefinition.Add(Builders<MongoMember>.Update.Set(u => u.LockoutWindowAttempts, 0));
|
||||
updateDefinition.Add(Builders<MongoMember>.Update.Set(u => u.LastLockoutDate, DateTime.MinValue));
|
||||
}
|
||||
|
||||
Task<UpdateResult> task = _memberCollection.UpdateOneAsync(Builders<MongoMember>.Filter.Eq(u => u.Id, id), Builders<MongoMember>.Update.Combine(updateDefinition));
|
||||
task.Wait();
|
||||
}
|
||||
|
||||
public override bool UnlockUser(string userName)
|
||||
{
|
||||
Task<UpdateResult> task = _memberCollection.UpdateOneAsync(Builders<MongoMember>.Filter.Eq(m => m.UserName.ToLower(), userName.ToLower()), Builders<MongoMember>.Update.Set(m => m.IsLockedOut, false));
|
||||
task.Wait();
|
||||
|
||||
return task.Result.IsAcknowledged && (task.Result.ModifiedCount == 1);
|
||||
}
|
||||
|
||||
public override void UpdateUser(MembershipUser user)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override bool ValidateUser(string username, string password)
|
||||
{
|
||||
Task<MongoMember> task = _memberCollection.Find(f => f.UserName.ToLower() == username.ToLower()).FirstOrDefaultAsync();
|
||||
task.Wait();
|
||||
MongoMember mm = task.Result;
|
||||
|
||||
if ((mm == null)
|
||||
|| !(mm.IsApproved && !mm.IsLockedOut))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
byte[] salt = mm.PassSalt;
|
||||
byte[] hash = CalculateHash(password, ref salt);
|
||||
|
||||
bool isFail = false;
|
||||
|
||||
for (int i = 0; i > hash.Length; i++)
|
||||
{
|
||||
isFail |= hash[i] != mm.PassHash[i];
|
||||
}
|
||||
|
||||
|
||||
if (isFail)
|
||||
{
|
||||
if (mm.LockoutWindowStart == DateTime.MinValue)
|
||||
public async Task<string> GenerateValidationHash(Guid id)
|
||||
{
|
||||
MongoMember mm = await _memberCollection.Find(Builders<MongoMember>.Filter.Eq(u => u.Id, id)).FirstOrDefaultAsync();
|
||||
if (mm == null)
|
||||
{
|
||||
mm.LockoutWindowStart = DateTime.Now;
|
||||
mm.LockoutWindowAttempts = 1;
|
||||
return null;
|
||||
}
|
||||
else
|
||||
|
||||
using (SHA256 sha = SHA256.Create())
|
||||
{
|
||||
if (mm.LockoutWindowStart.AddMinutes(PasswordAttemptWindow) > DateTime.Now)
|
||||
{
|
||||
// still within window
|
||||
mm.LockoutWindowAttempts++;
|
||||
if (mm.LockoutWindowAttempts >= MaxInvalidPasswordAttempts)
|
||||
{
|
||||
mm.IsLockedOut = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// outside of window, reset
|
||||
mm.LockoutWindowStart = DateTime.Now;
|
||||
mm.LockoutWindowAttempts = 1;
|
||||
}
|
||||
string content = $"{mm.Id}.{mm.PassSalt}.{ConfigurationManager.AppSettings["data:SecretKey"]}";
|
||||
byte[] hashBytes = sha.ComputeHash(Encoding.UTF8.GetBytes(content));
|
||||
|
||||
return Base32Encoding.ToString(hashBytes);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mm.LastLoginDate = DateTime.Now;
|
||||
mm.LockoutWindowStart = DateTime.MinValue;
|
||||
mm.LockoutWindowAttempts = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Task<ReplaceOneResult> updTask = _memberCollection.ReplaceOneAsync(Builders<MongoMember>.Filter.Eq(u => u.Id, mm.Id), mm);
|
||||
updTask.Wait();
|
||||
public async Task<bool> ValidateUserFromHash(Guid id, string validate)
|
||||
{
|
||||
MongoMember mm = await _memberCollection.Find(Builders<MongoMember>.Filter.Eq(u => u.Id, id)).FirstOrDefaultAsync();
|
||||
if (mm == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !isFail;
|
||||
}
|
||||
using (SHA256 sha = SHA256.Create())
|
||||
{
|
||||
string content = $"{mm.Id}.{mm.PassSalt}.{ConfigurationManager.AppSettings["data:SecretKey"]}";
|
||||
byte[] hashBytes = sha.ComputeHash(Encoding.UTF8.GetBytes(content));
|
||||
|
||||
private static byte[] CalculateHash(string password, ref byte[] salt)
|
||||
{
|
||||
if (!salt.Any(v => v != 0))
|
||||
{
|
||||
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
|
||||
rng.GetBytes(salt);
|
||||
}
|
||||
string expected = Base32Encoding.ToString(hashBytes);
|
||||
|
||||
byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
|
||||
bool success = string.Equals(expected, validate, StringComparison.InvariantCultureIgnoreCase);
|
||||
|
||||
var hashPlaintext = new byte[salt.Length + passwordBytes.Length];
|
||||
if (success)
|
||||
{
|
||||
ChangeApproval(id, true);
|
||||
}
|
||||
|
||||
passwordBytes.CopyTo(hashPlaintext, 0);
|
||||
salt.CopyTo(hashPlaintext, passwordBytes.Length);
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
SHA512CryptoServiceProvider sha = new SHA512CryptoServiceProvider();
|
||||
byte[] hash = sha.ComputeHash(hashPlaintext);
|
||||
private static byte[] CalculateHash(string password, ref byte[] salt)
|
||||
{
|
||||
if (!salt.Any(v => v != 0))
|
||||
{
|
||||
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
|
||||
rng.GetBytes(salt);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
|
||||
|
||||
private static bool TryReadBool(string config, bool defaultValue)
|
||||
{
|
||||
bool temp;
|
||||
bool success = bool.TryParse(config, out temp);
|
||||
return success
|
||||
? temp
|
||||
: defaultValue;
|
||||
}
|
||||
var hashPlaintext = new byte[salt.Length + passwordBytes.Length];
|
||||
|
||||
private static int TryReadInt(string config, int defaultValue)
|
||||
{
|
||||
int temp;
|
||||
bool success = int.TryParse(config, out temp);
|
||||
return success
|
||||
? temp
|
||||
: defaultValue;
|
||||
}
|
||||
}
|
||||
passwordBytes.CopyTo(hashPlaintext, 0);
|
||||
salt.CopyTo(hashPlaintext, passwordBytes.Length);
|
||||
|
||||
public class MongoMember
|
||||
{
|
||||
[BsonId]
|
||||
public Guid Id { get; set; }
|
||||
SHA512CryptoServiceProvider sha = new SHA512CryptoServiceProvider();
|
||||
byte[] hash = sha.ComputeHash(hashPlaintext);
|
||||
|
||||
public string UserName { get; set; }
|
||||
public byte[] PassHash { get; set; }
|
||||
public byte[] PassSalt { get; set; }
|
||||
public string EmailAddress { get; set; }
|
||||
return hash;
|
||||
}
|
||||
|
||||
public bool IsApproved { get; set; }
|
||||
public bool IsLockedOut { get; set; }
|
||||
private static bool TryReadBool(string config, bool defaultValue)
|
||||
{
|
||||
bool temp;
|
||||
bool success = bool.TryParse(config, out temp);
|
||||
return success
|
||||
? temp
|
||||
: defaultValue;
|
||||
}
|
||||
|
||||
public DateTime CreationDate { get; set; }
|
||||
public DateTime LastActivityDate { get; set; }
|
||||
public DateTime LastLockoutDate { get; set; }
|
||||
public DateTime LastLoginDate { get; set; }
|
||||
private static int TryReadInt(string config, int defaultValue)
|
||||
{
|
||||
int temp;
|
||||
bool success = int.TryParse(config, out temp);
|
||||
return success
|
||||
? temp
|
||||
: defaultValue;
|
||||
}
|
||||
|
||||
public DateTime LockoutWindowStart { get; set; }
|
||||
public int LockoutWindowAttempts { get; set; }
|
||||
}
|
||||
private static DateTime FixupDatesFromMongo(DateTime dt)
|
||||
{
|
||||
DateTime local = DateTime.SpecifyKind(dt, DateTimeKind.Local);
|
||||
return local;
|
||||
}
|
||||
}
|
||||
|
||||
public class MongoMember
|
||||
{
|
||||
[BsonId]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string UserName { get; set; }
|
||||
public byte[] PassHash { get; set; }
|
||||
public byte[] PassSalt { get; set; }
|
||||
public string EmailAddress { get; set; }
|
||||
|
||||
public bool IsApproved { get; set; }
|
||||
public bool IsLockedOut { get; set; }
|
||||
|
||||
public DateTime CreationDate { get; set; }
|
||||
public DateTime LastActivityDate { get; set; }
|
||||
public DateTime LastLockoutDate { get; set; }
|
||||
public DateTime LastLoginDate { get; set; }
|
||||
|
||||
public DateTime LockoutWindowStart { get; set; }
|
||||
public int LockoutWindowAttempts { get; set; }
|
||||
}
|
||||
}
|
|
@ -13,214 +13,221 @@
|
|||
|
||||
namespace MongoAuth
|
||||
{
|
||||
public class MongoRoleProvider : RoleProvider
|
||||
{
|
||||
private const string MEMBER_COLLECTION_NAME = "members";
|
||||
private const string ROLE_COLLECTION_NAME = "roles";
|
||||
private IMongoCollection<MongoMember> _memberCollection;
|
||||
private IMongoCollection<MongoRole> _roleCollection;
|
||||
public class MongoRoleProvider : RoleProvider
|
||||
{
|
||||
private const string MEMBER_COLLECTION_NAME = "members";
|
||||
private const string ROLE_COLLECTION_NAME = "roles";
|
||||
private IMongoCollection<MongoMember> _memberCollection;
|
||||
private IMongoCollection<MongoRole> _roleCollection;
|
||||
|
||||
public override string ApplicationName
|
||||
{
|
||||
get { return ""; }
|
||||
set { }
|
||||
}
|
||||
public override string ApplicationName
|
||||
{
|
||||
get { return ""; }
|
||||
set { }
|
||||
}
|
||||
|
||||
public override void Initialize(string name, NameValueCollection config)
|
||||
{
|
||||
base.Initialize(name, config);
|
||||
public override void Initialize(string name, NameValueCollection config)
|
||||
{
|
||||
base.Initialize(name, config);
|
||||
|
||||
MongoClientSettings settings = new MongoClientSettings
|
||||
{
|
||||
Server = new MongoServerAddress(DatabaseConfig.Host, DatabaseConfig.Port)
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(DatabaseConfig.Username) && !string.IsNullOrEmpty(DatabaseConfig.Password))
|
||||
{
|
||||
settings.Credentials = new List<MongoCredential>
|
||||
MongoClientSettings settings = new MongoClientSettings
|
||||
{
|
||||
MongoCredential.CreateCredential(DatabaseConfig.Database, DatabaseConfig.Username, DatabaseConfig.Password)
|
||||
Server = new MongoServerAddress(DatabaseConfig.Host, DatabaseConfig.Port)
|
||||
};
|
||||
}
|
||||
|
||||
MongoClient dbClient = new MongoClient(settings);
|
||||
|
||||
_roleCollection = dbClient.GetDatabase(DatabaseConfig.Database).GetCollection<MongoRole>(ROLE_COLLECTION_NAME);
|
||||
_memberCollection = dbClient.GetDatabase(DatabaseConfig.Database).GetCollection<MongoMember>(MEMBER_COLLECTION_NAME);
|
||||
}
|
||||
|
||||
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
|
||||
{
|
||||
Task<List<MongoRole>> roleTask = _roleCollection.Find(r => roleNames.Contains(r.RoleName)).ToListAsync();
|
||||
roleTask.Wait();
|
||||
List<MongoRole> roles = roleTask.Result;
|
||||
|
||||
Task<List<MongoMember>> userTask = _memberCollection.Find(u => usernames.Contains(u.UserName)).ToListAsync();
|
||||
userTask.Wait();
|
||||
List<MongoMember> users = userTask.Result;
|
||||
|
||||
for (int i = 0; i < roles.Count; i++)
|
||||
{
|
||||
var newUsers = new List<Guid>();
|
||||
|
||||
if (roles[i].Users != null)
|
||||
if (!string.IsNullOrEmpty(DatabaseConfig.Username) && !string.IsNullOrEmpty(DatabaseConfig.Password))
|
||||
{
|
||||
newUsers.AddRange(roles[i].Users);
|
||||
settings.Credentials = new List<MongoCredential>
|
||||
{
|
||||
MongoCredential.CreateCredential(DatabaseConfig.Database, DatabaseConfig.Username, DatabaseConfig.Password)
|
||||
};
|
||||
}
|
||||
|
||||
IEnumerable<Guid> usersToAdd = from u in users
|
||||
where newUsers.All(v => v != u.Id)
|
||||
select u.Id;
|
||||
MongoClient dbClient = new MongoClient(settings);
|
||||
|
||||
newUsers.AddRange(usersToAdd);
|
||||
_roleCollection = dbClient.GetDatabase(DatabaseConfig.Database).GetCollection<MongoRole>(ROLE_COLLECTION_NAME);
|
||||
_memberCollection = dbClient.GetDatabase(DatabaseConfig.Database).GetCollection<MongoMember>(MEMBER_COLLECTION_NAME);
|
||||
}
|
||||
|
||||
roles[i].Users = newUsers.ToArray();
|
||||
public override void AddUsersToRoles(string[] usernames, string[] roleNames)
|
||||
{
|
||||
Task<List<MongoRole>> roleTask = _roleCollection.Find(r => roleNames.Contains(r.RoleName)).ToListAsync();
|
||||
roleTask.Wait();
|
||||
List<MongoRole> roles = roleTask.Result;
|
||||
|
||||
Task<ReplaceOneResult> update = _roleCollection.ReplaceOneAsync(Builders<MongoRole>.Filter.Eq(r => r.Id, roles[i].Id), roles[i]);
|
||||
update.Wait();
|
||||
}
|
||||
}
|
||||
Task<List<MongoMember>> userTask = _memberCollection.Find(u => usernames.Contains(u.UserName)).ToListAsync();
|
||||
userTask.Wait();
|
||||
List<MongoMember> users = userTask.Result;
|
||||
|
||||
public override void CreateRole(string roleName)
|
||||
{
|
||||
MongoRole r = new MongoRole
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
RoleName = roleName
|
||||
};
|
||||
for (int i = 0; i < roles.Count; i++)
|
||||
{
|
||||
var newUsers = new List<Guid>();
|
||||
|
||||
Task task = _roleCollection.InsertOneAsync(r);
|
||||
task.Wait();
|
||||
}
|
||||
if (roles[i].Users != null)
|
||||
{
|
||||
newUsers.AddRange(roles[i].Users);
|
||||
}
|
||||
|
||||
public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
|
||||
{
|
||||
Task<MongoRole> role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync();
|
||||
role.Wait();
|
||||
IEnumerable<Guid> usersToAdd = from u in users
|
||||
where newUsers.All(v => v != u.Id)
|
||||
select u.Id;
|
||||
|
||||
if ((role.Result != null)
|
||||
&& (role.Result.Users.Length > 0)
|
||||
&& throwOnPopulatedRole)
|
||||
{
|
||||
throw new ProviderException(Resources.RoleNotEmpty);
|
||||
}
|
||||
newUsers.AddRange(usersToAdd);
|
||||
|
||||
Task<DeleteResult> task = _roleCollection.DeleteOneAsync(r => r.RoleName == roleName);
|
||||
task.Wait();
|
||||
roles[i].Users = newUsers.ToArray();
|
||||
|
||||
return true;
|
||||
}
|
||||
Task<ReplaceOneResult> update = _roleCollection.ReplaceOneAsync(Builders<MongoRole>.Filter.Eq(r => r.Id, roles[i].Id), roles[i]);
|
||||
update.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
public override string[] FindUsersInRole(string roleName, string usernameToMatch)
|
||||
{
|
||||
Task<MongoRole> role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync();
|
||||
role.Wait();
|
||||
public override void CreateRole(string roleName)
|
||||
{
|
||||
Task<MongoRole> role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync();
|
||||
role.Wait();
|
||||
if (role.Result != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (role.Result == null)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
MongoRole mr = new MongoRole
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
RoleName = roleName
|
||||
};
|
||||
|
||||
Task<List<MongoMember>> users = _memberCollection.Find(u => role.Result.Users.Contains(u.Id) && u.UserName.ToLower().Contains(usernameToMatch.ToLower())).ToListAsync();
|
||||
users.Wait();
|
||||
Task task = _roleCollection.InsertOneAsync(mr);
|
||||
task.Wait();
|
||||
}
|
||||
|
||||
return users.Result.Select(r => r.UserName).ToArray();
|
||||
}
|
||||
public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
|
||||
{
|
||||
Task<MongoRole> role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync();
|
||||
role.Wait();
|
||||
|
||||
public override string[] GetAllRoles()
|
||||
{
|
||||
Task<List<MongoRole>> roles = _roleCollection.Find(new BsonDocument()).ToListAsync();
|
||||
roles.Wait();
|
||||
if (role.Result != null
|
||||
&& role.Result.Users.Length > 0
|
||||
&& throwOnPopulatedRole)
|
||||
{
|
||||
throw new ProviderException(Resources.RoleNotEmpty);
|
||||
}
|
||||
|
||||
return roles.Result.Select(r => r.RoleName).ToArray();
|
||||
}
|
||||
Task<DeleteResult> task = _roleCollection.DeleteOneAsync(r => r.RoleName == roleName);
|
||||
task.Wait();
|
||||
|
||||
public override string[] GetRolesForUser(string username)
|
||||
{
|
||||
Task<MongoMember> user = _memberCollection.Find(u => u.UserName.ToLower() == username.ToLower()).SingleOrDefaultAsync();
|
||||
user.Wait();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (user.Result == null)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
public override string[] FindUsersInRole(string roleName, string usernameToMatch)
|
||||
{
|
||||
Task<MongoRole> role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync();
|
||||
role.Wait();
|
||||
|
||||
Task<List<MongoRole>> role = _roleCollection.Find(r => (r.Users != null) && r.Users.Contains(user.Result.Id)).ToListAsync();
|
||||
role.Wait();
|
||||
if (role.Result == null)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
return role.Result.Select(r => r.RoleName).ToArray();
|
||||
}
|
||||
Task<List<MongoMember>> users = _memberCollection.Find(u => role.Result.Users.Contains(u.Id) && u.UserName.ToLower().Contains(usernameToMatch.ToLower())).ToListAsync();
|
||||
users.Wait();
|
||||
|
||||
public override string[] GetUsersInRole(string roleName)
|
||||
{
|
||||
Task<MongoRole> role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync();
|
||||
role.Wait();
|
||||
return users.Result.Select(r => r.UserName).ToArray();
|
||||
}
|
||||
|
||||
if (role.Result == null)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
public override string[] GetAllRoles()
|
||||
{
|
||||
Task<List<MongoRole>> roles = _roleCollection.Find(new BsonDocument()).ToListAsync();
|
||||
roles.Wait();
|
||||
|
||||
Task<List<MongoMember>> users = _memberCollection.Find(u => role.Result.Users.Contains(u.Id)).ToListAsync();
|
||||
users.Wait();
|
||||
return roles.Result.Select(r => r.RoleName).ToArray();
|
||||
}
|
||||
|
||||
return users.Result.Select(u => u.UserName).ToArray();
|
||||
}
|
||||
public override string[] GetRolesForUser(string username)
|
||||
{
|
||||
Task<MongoMember> user = _memberCollection.Find(u => u.UserName.ToLower() == username.ToLower()).SingleOrDefaultAsync();
|
||||
user.Wait();
|
||||
|
||||
public override bool IsUserInRole(string username, string roleName)
|
||||
{
|
||||
Task<MongoMember> user = _memberCollection.Find(u => u.UserName.ToLower() == username.ToLower()).SingleOrDefaultAsync();
|
||||
Task<MongoRole> role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync();
|
||||
user.Wait();
|
||||
role.Wait();
|
||||
if (user.Result == null)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
if ((user.Result == null)
|
||||
|| (role.Result?.Users == null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Task<List<MongoRole>> role = _roleCollection.Find(r => r.Users != null && r.Users.Contains(user.Result.Id)).ToListAsync();
|
||||
role.Wait();
|
||||
|
||||
return role.Result.Users.Contains(user.Result.Id);
|
||||
}
|
||||
return role.Result.Select(r => r.RoleName).ToArray();
|
||||
}
|
||||
|
||||
public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
|
||||
{
|
||||
Task<List<MongoRole>> roleTask = _roleCollection.Find(r => roleNames.Contains(r.RoleName)).ToListAsync();
|
||||
roleTask.Wait();
|
||||
List<MongoRole> roles = roleTask.Result;
|
||||
public override string[] GetUsersInRole(string roleName)
|
||||
{
|
||||
Task<MongoRole> role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync();
|
||||
role.Wait();
|
||||
|
||||
Task<List<MongoMember>> userTask = _memberCollection.Find(u => usernames.Contains(u.UserName)).ToListAsync();
|
||||
userTask.Wait();
|
||||
List<MongoMember> users = userTask.Result;
|
||||
if (role.Result == null)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
foreach (MongoRole t in roles)
|
||||
{
|
||||
t.Users = (from u in t.Users
|
||||
where users.All(v => v.Id != u)
|
||||
select u).ToArray();
|
||||
Task<List<MongoMember>> users = _memberCollection.Find(u => role.Result.Users.Contains(u.Id)).ToListAsync();
|
||||
users.Wait();
|
||||
|
||||
Task<ReplaceOneResult> update = _roleCollection.ReplaceOneAsync(Builders<MongoRole>.Filter.Eq(r => r.Id, t.Id), t);
|
||||
update.Wait();
|
||||
}
|
||||
}
|
||||
return users.Result.Select(u => u.UserName).ToArray();
|
||||
}
|
||||
|
||||
public override bool RoleExists(string roleName)
|
||||
{
|
||||
Task<MongoRole> role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync();
|
||||
role.Wait();
|
||||
public override bool IsUserInRole(string username, string roleName)
|
||||
{
|
||||
Task<MongoMember> user = _memberCollection.Find(u => u.UserName.ToLower() == username.ToLower()).SingleOrDefaultAsync();
|
||||
Task<MongoRole> role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync();
|
||||
user.Wait();
|
||||
role.Wait();
|
||||
|
||||
return role.Result != null;
|
||||
}
|
||||
}
|
||||
if (user.Result == null
|
||||
|| role.Result?.Users == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
[DataObject]
|
||||
public class MongoRole
|
||||
{
|
||||
[BsonId]
|
||||
public Guid Id { get; set; }
|
||||
return role.Result.Users.Contains(user.Result.Id);
|
||||
}
|
||||
|
||||
public string RoleName { get; set; }
|
||||
public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
|
||||
{
|
||||
Task<List<MongoRole>> roleTask = _roleCollection.Find(r => roleNames.Contains(r.RoleName)).ToListAsync();
|
||||
roleTask.Wait();
|
||||
List<MongoRole> roles = roleTask.Result;
|
||||
|
||||
public Guid[] Users { get; set; }
|
||||
}
|
||||
Task<List<MongoMember>> userTask = _memberCollection.Find(u => usernames.Contains(u.UserName)).ToListAsync();
|
||||
userTask.Wait();
|
||||
List<MongoMember> users = userTask.Result;
|
||||
|
||||
foreach (MongoRole t in roles)
|
||||
{
|
||||
t.Users = (from u in t.Users
|
||||
where users.All(v => v.Id != u)
|
||||
select u).ToArray();
|
||||
|
||||
Task<ReplaceOneResult> update = _roleCollection.ReplaceOneAsync(Builders<MongoRole>.Filter.Eq(r => r.Id, t.Id), t);
|
||||
update.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool RoleExists(string roleName)
|
||||
{
|
||||
Task<MongoRole> role = _roleCollection.Find(r => r.RoleName == roleName).SingleOrDefaultAsync();
|
||||
role.Wait();
|
||||
|
||||
return role.Result != null;
|
||||
}
|
||||
}
|
||||
|
||||
[DataObject]
|
||||
public class MongoRole
|
||||
{
|
||||
[BsonId]
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public string RoleName { get; set; }
|
||||
|
||||
public Guid[] Users { get; set; }
|
||||
}
|
||||
}
|
152
BuildFeed.Local/VariantTerms.Designer.cs
generated
152
BuildFeed.Local/VariantTerms.Designer.cs
generated
|
@ -186,6 +186,15 @@ public static string Common_Admin {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Change password.
|
||||
/// </summary>
|
||||
public static string Common_ChangePassword {
|
||||
get {
|
||||
return ResourceManager.GetString("Common_ChangePassword", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Contribute on {0}.
|
||||
/// </summary>
|
||||
|
@ -330,6 +339,32 @@ public static string Common_Twitter {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Thank you for registering to {0}.
|
||||
///
|
||||
///Please verify your email address by clicking the link below, or by copying and pasting it into your browser.
|
||||
///{1}
|
||||
///
|
||||
///If you did not register to {0}, you can ignore this email.
|
||||
///
|
||||
///Thanks,
|
||||
///The {0} Team..
|
||||
/// </summary>
|
||||
public static string Email_Registration_Body {
|
||||
get {
|
||||
return ResourceManager.GetString("Email_Registration_Body", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0}: Please verify your email address.
|
||||
/// </summary>
|
||||
public static string Email_Registration_Subject {
|
||||
get {
|
||||
return ResourceManager.GetString("Email_Registration_Subject", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to About.
|
||||
/// </summary>
|
||||
|
@ -924,15 +959,6 @@ public static string Search_Year {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Every account is validated by an administrator, so be patient and check again later.
|
||||
/// </summary>
|
||||
public static string Support_AccountValidation {
|
||||
get {
|
||||
return ResourceManager.GetString("Support_AccountValidation", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Additions to BuildFeed.
|
||||
/// </summary>
|
||||
|
@ -987,6 +1013,24 @@ public static string Support_EmailAddress {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to An validation link has been sent to your email address. Please click the link in the email to verify the account..
|
||||
/// </summary>
|
||||
public static string Support_EmailValidationContent {
|
||||
get {
|
||||
return ResourceManager.GetString("Support_EmailValidationContent", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Please verify your email address.
|
||||
/// </summary>
|
||||
public static string Support_EmailValidationTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("Support_EmailValidationTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Enter current password.
|
||||
/// </summary>
|
||||
|
@ -1014,6 +1058,51 @@ public static string Support_EnterPassword {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to An error occurred when attempting to change your password..
|
||||
/// </summary>
|
||||
public static string Support_Error_ChangingPasswordFail {
|
||||
get {
|
||||
return ResourceManager.GetString("Support_Error_ChangingPasswordFail", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to A user account with this email address already exists..
|
||||
/// </summary>
|
||||
public static string Support_Error_DuplicateEmail {
|
||||
get {
|
||||
return ResourceManager.GetString("Support_Error_DuplicateEmail", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to A user account with this user name already exists..
|
||||
/// </summary>
|
||||
public static string Support_Error_DuplicateUserName {
|
||||
get {
|
||||
return ResourceManager.GetString("Support_Error_DuplicateUserName", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The password you have entered is invalid..
|
||||
/// </summary>
|
||||
public static string Support_Error_InvalidPassword {
|
||||
get {
|
||||
return ResourceManager.GetString("Support_Error_InvalidPassword", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to An unknown error has occurred..
|
||||
/// </summary>
|
||||
public static string Support_Error_UnknownError {
|
||||
get {
|
||||
return ResourceManager.GetString("Support_Error_UnknownError", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Highest version.
|
||||
/// </summary>
|
||||
|
@ -1095,15 +1184,6 @@ public static string Support_RememberMe {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Thank you for registering.
|
||||
/// </summary>
|
||||
public static string Support_ThanksRegister {
|
||||
get {
|
||||
return ResourceManager.GetString("Support_ThanksRegister", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Username.
|
||||
/// </summary>
|
||||
|
@ -1113,6 +1193,42 @@ public static string Support_UserName {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Please check the validation link in your email and try again..
|
||||
/// </summary>
|
||||
public static string Support_ValidationFailureContent {
|
||||
get {
|
||||
return ResourceManager.GetString("Support_ValidationFailureContent", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to We were unable to validate your account..
|
||||
/// </summary>
|
||||
public static string Support_ValidationFailureTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("Support_ValidationFailureTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to You will now be able to log in using your username and password..
|
||||
/// </summary>
|
||||
public static string Support_ValidationSuccessContent {
|
||||
get {
|
||||
return ResourceManager.GetString("Support_ValidationSuccessContent", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to You have successfully validated your account..
|
||||
/// </summary>
|
||||
public static string Support_ValidationSuccessTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("Support_ValidationSuccessTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Week.
|
||||
/// </summary>
|
||||
|
|
|
@ -159,6 +159,9 @@
|
|||
<data name="Common_Admin" xml:space="preserve">
|
||||
<value>[!!! Âδ₥ïñ !!!]</value>
|
||||
</data>
|
||||
<data name="Common_ChangePassword" xml:space="preserve">
|
||||
<value>[!!! Çλáñϱè ƥáƨƨωôřδ ℓ !!!]</value>
|
||||
</data>
|
||||
<data name="Common_ContributeOn" xml:space="preserve">
|
||||
<value>[!!! Çôñƭřïβúƭè ôñ {0} !!!]</value>
|
||||
</data>
|
||||
|
@ -207,6 +210,20 @@
|
|||
<data name="Common_Twitter" xml:space="preserve">
|
||||
<value>[!!! Tωïƭƭèř !!!]</value>
|
||||
</data>
|
||||
<data name="Email_Registration_Body" xml:space="preserve">
|
||||
<value>[!!! Tλáñƙ ¥ôú ƒôř řèϱïƨƭèřïñϱ ƭô {0}. ℓôřè !!!]
|
||||
[!!! !!!]
|
||||
[!!! Þℓèáƨè Ʋèř ¥ôúř è₥áïℓ áδδřèƨƨ β¥ çℓïçƙïñϱ ƭλè ℓïñƙ βèℓôω, ôř β¥ çôƥ¥ïñϱ áñδ ƥáƨƭïñϱ ïƭ ïñƭô ¥ôúř βřôωƨèř. ℓôřè₥ ïƥƨú₥ δôℓ !!!]
|
||||
[!!! {1} !!!]
|
||||
[!!! !!!]
|
||||
[!!! ̃ ¥ôú δïδ ñôƭ řèϱïƨƭèř ƭô {0}, ¥ôú çáñ ïϱñôřè ƭλïƨ è₥áïℓ. ℓôřè₥ ïƥ !!!]
|
||||
[!!! !!!]
|
||||
[!!! Tλáñƙƨ, !!!]
|
||||
[!!! Tλè {0} Tèá₥. ℓ !!!]</value>
|
||||
</data>
|
||||
<data name="Email_Registration_Subject" xml:space="preserve">
|
||||
<value>[!!! {0}: Þℓèáƨè Ʋèř ¥ôúř è₥áïℓ áδδřèƨƨ ℓôřè₥ !!!]</value>
|
||||
</data>
|
||||
<data name="Front_About" xml:space="preserve">
|
||||
<value>[!!! Âβôúƭ !!!]</value>
|
||||
</data>
|
||||
|
@ -405,9 +422,6 @@
|
|||
<data name="Search_Year" xml:space="preserve">
|
||||
<value>[!!! Ýèář !!!]</value>
|
||||
</data>
|
||||
<data name="Support_AccountValidation" xml:space="preserve">
|
||||
<value>[!!! ÉƲèř¥ áççôúñƭ ïƨ Ʋáℓïδáƭèδ β¥ áñ Âδ₥ïñïƨƭřáƭôř, ƨô βè ƥáƭïèñƭ áñδ çλèçƙ áϱáïñ ℓáƭèř. ℓôřè₥ ï !!!]</value>
|
||||
</data>
|
||||
<data name="Support_AdditionsToBuildFeed" xml:space="preserve">
|
||||
<value>[!!! Âδδïƭïôñƨ ƭô ßúïℓδFèèδ ℓ !!!]</value>
|
||||
</data>
|
||||
|
@ -426,6 +440,12 @@
|
|||
<data name="Support_EmailAddress" xml:space="preserve">
|
||||
<value>[!!! É₥áïℓ áδδřèƨƨ !!!]</value>
|
||||
</data>
|
||||
<data name="Support_EmailValidationContent" xml:space="preserve">
|
||||
<value>[!!! Âñ Ʋáℓïδáƭïôñ ℓïñƙ λáƨ βèèñ ƨèñƭ ƭô ¥ôúř è₥áïℓ áδδřèƨƨ. Þℓèáƨè çℓïçƙ ƭλè ℓïñƙ ïñ ƭλè è₥áïℓ ƭô Ʋèř ƭλè áççôúñƭ. ℓôřè₥ ïƥƨú₥ δôℓô !!!]</value>
|
||||
</data>
|
||||
<data name="Support_EmailValidationTitle" xml:space="preserve">
|
||||
<value>[!!! Þℓèáƨè Ʋèř ¥ôúř è₥áïℓ áδδřèƨƨ ℓôřè !!!]</value>
|
||||
</data>
|
||||
<data name="Support_EnterCurrentPassword" xml:space="preserve">
|
||||
<value>[!!! Éñƭèř çúřřèñƭ ƥáƨƨωôřδ ℓ !!!]</value>
|
||||
</data>
|
||||
|
@ -435,6 +455,21 @@
|
|||
<data name="Support_EnterPassword" xml:space="preserve">
|
||||
<value>[!!! Éñƭèř ƥáƨƨωôřδ !!!]</value>
|
||||
</data>
|
||||
<data name="Support_Error_ChangingPasswordFail" xml:space="preserve">
|
||||
<value>[!!! Âñ èřřôř ôççúřřèδ ωλèñ áƭƭè₥ƥƭïñϱ ƭô çλáñϱè ¥ôúř ƥáƨƨωôřδ. ℓôřè₥ ïƥ !!!]</value>
|
||||
</data>
|
||||
<data name="Support_Error_DuplicateEmail" xml:space="preserve">
|
||||
<value>[!!! Â úƨèř áççôúñƭ ωïƭλ ƭλïƨ è₥áïℓ áδδřèƨƨ áℓřèáδ¥ èжïƨƭƨ. ℓôřè₥ ï !!!]</value>
|
||||
</data>
|
||||
<data name="Support_Error_DuplicateUserName" xml:space="preserve">
|
||||
<value>[!!! Â úƨèř áççôúñƭ ωïƭλ ƭλïƨ úƨèř ñá₥è áℓřèáδ¥ èжïƨƭƨ. ℓôřè₥ ï !!!]</value>
|
||||
</data>
|
||||
<data name="Support_Error_InvalidPassword" xml:space="preserve">
|
||||
<value>[!!! Tλè ƥáƨƨωôřδ ¥ôú λáƲè èñƭèřèδ ïƨ ïñƲáℓïδ. ℓôřè₥ !!!]</value>
|
||||
</data>
|
||||
<data name="Support_Error_UnknownError" xml:space="preserve">
|
||||
<value>[!!! Âñ úñƙñôωñ èřřôř λáƨ ôççúřřèδ. ℓôřè !!!]</value>
|
||||
</data>
|
||||
<data name="Support_HighestVersion" xml:space="preserve">
|
||||
<value>[!!! Hïϱλèƨƭ Ʋèřƨïôñ ℓ !!!]</value>
|
||||
</data>
|
||||
|
@ -462,12 +497,21 @@
|
|||
<data name="Support_RememberMe" xml:space="preserve">
|
||||
<value>[!!! Rè₥è₥βèř ₥è !!!]</value>
|
||||
</data>
|
||||
<data name="Support_ThanksRegister" xml:space="preserve">
|
||||
<value>[!!! Tλáñƙ ¥ôú ƒôř řèϱïƨƭèřïñϱ ℓô !!!]</value>
|
||||
</data>
|
||||
<data name="Support_UserName" xml:space="preserve">
|
||||
<value>[!!! Ûƨèřñá₥è !!!]</value>
|
||||
</data>
|
||||
<data name="Support_ValidationFailureContent" xml:space="preserve">
|
||||
<value>[!!! Þℓèáƨè çλèçƙ ƭλè Ʋáℓïδáƭïôñ ℓïñƙ ïñ ¥ôúř è₥áïℓ áñδ ƭř¥ áϱáïñ. ℓôřè₥ ïƥ !!!]</value>
|
||||
</data>
|
||||
<data name="Support_ValidationFailureTitle" xml:space="preserve">
|
||||
<value>[!!! Wè ωèřè úñáβℓè ƭô Ʋáℓïδáƭè ¥ôúř áççôúñƭ. ℓôřè₥ !!!]</value>
|
||||
</data>
|
||||
<data name="Support_ValidationSuccessContent" xml:space="preserve">
|
||||
<value>[!!! Ýôú ωïℓℓ ñôω βè áβℓè ƭô ℓôϱ ïñ úƨïñϱ ¥ôúř úƨèřñá₥è áñδ ƥáƨƨωôřδ. ℓôřè₥ ïƥƨ !!!]</value>
|
||||
</data>
|
||||
<data name="Support_ValidationSuccessTitle" xml:space="preserve">
|
||||
<value>[!!! Ýôú λáƲè ƨúççèƨƨƒúℓℓ¥ Ʋáℓïδáƭèδ ¥ôúř áççôúñƭ. ℓôřè₥ !!!]</value>
|
||||
</data>
|
||||
<data name="Support_Week" xml:space="preserve">
|
||||
<value>[!!! Wèèƙ !!!]</value>
|
||||
</data>
|
||||
|
|
|
@ -159,6 +159,9 @@
|
|||
<data name="Common_Admin" xml:space="preserve">
|
||||
<value>Admin</value>
|
||||
</data>
|
||||
<data name="Common_ChangePassword" xml:space="preserve">
|
||||
<value>Change password</value>
|
||||
</data>
|
||||
<data name="Common_ContributeOn" xml:space="preserve">
|
||||
<value>Contribute on {0}</value>
|
||||
</data>
|
||||
|
@ -207,6 +210,20 @@
|
|||
<data name="Common_Twitter" xml:space="preserve">
|
||||
<value>Twitter</value>
|
||||
</data>
|
||||
<data name="Email_Registration_Body" xml:space="preserve">
|
||||
<value>Thank you for registering to {0}.
|
||||
|
||||
Please verify your email address by clicking the link below, or by copying and pasting it into your browser.
|
||||
{1}
|
||||
|
||||
If you did not register to {0}, you can ignore this email.
|
||||
|
||||
Thanks,
|
||||
The {0} Team.</value>
|
||||
</data>
|
||||
<data name="Email_Registration_Subject" xml:space="preserve">
|
||||
<value>{0}: Please verify your email address</value>
|
||||
</data>
|
||||
<data name="Front_About" xml:space="preserve">
|
||||
<value>About</value>
|
||||
</data>
|
||||
|
@ -405,9 +422,6 @@
|
|||
<data name="Search_Year" xml:space="preserve">
|
||||
<value>Year</value>
|
||||
</data>
|
||||
<data name="Support_AccountValidation" xml:space="preserve">
|
||||
<value>Every account is validated by an administrator, so be patient and check again later</value>
|
||||
</data>
|
||||
<data name="Support_AdditionsToBuildFeed" xml:space="preserve">
|
||||
<value>Additions to BuildFeed</value>
|
||||
</data>
|
||||
|
@ -426,6 +440,12 @@
|
|||
<data name="Support_EmailAddress" xml:space="preserve">
|
||||
<value>Email address</value>
|
||||
</data>
|
||||
<data name="Support_EmailValidationContent" xml:space="preserve">
|
||||
<value>An validation link has been sent to your email address. Please click the link in the email to verify the account.</value>
|
||||
</data>
|
||||
<data name="Support_EmailValidationTitle" xml:space="preserve">
|
||||
<value>Please verify your email address</value>
|
||||
</data>
|
||||
<data name="Support_EnterCurrentPassword" xml:space="preserve">
|
||||
<value>Enter current password</value>
|
||||
</data>
|
||||
|
@ -435,6 +455,21 @@
|
|||
<data name="Support_EnterPassword" xml:space="preserve">
|
||||
<value>Enter password</value>
|
||||
</data>
|
||||
<data name="Support_Error_ChangingPasswordFail" xml:space="preserve">
|
||||
<value>An error occurred when attempting to change your password.</value>
|
||||
</data>
|
||||
<data name="Support_Error_DuplicateEmail" xml:space="preserve">
|
||||
<value>A user account with this email address already exists.</value>
|
||||
</data>
|
||||
<data name="Support_Error_DuplicateUserName" xml:space="preserve">
|
||||
<value>A user account with this user name already exists.</value>
|
||||
</data>
|
||||
<data name="Support_Error_InvalidPassword" xml:space="preserve">
|
||||
<value>The password you have entered is invalid.</value>
|
||||
</data>
|
||||
<data name="Support_Error_UnknownError" xml:space="preserve">
|
||||
<value>An unknown error has occurred.</value>
|
||||
</data>
|
||||
<data name="Support_HighestVersion" xml:space="preserve">
|
||||
<value>Highest version</value>
|
||||
</data>
|
||||
|
@ -462,12 +497,21 @@
|
|||
<data name="Support_RememberMe" xml:space="preserve">
|
||||
<value>Remember me</value>
|
||||
</data>
|
||||
<data name="Support_ThanksRegister" xml:space="preserve">
|
||||
<value>Thank you for registering</value>
|
||||
</data>
|
||||
<data name="Support_UserName" xml:space="preserve">
|
||||
<value>Username</value>
|
||||
</data>
|
||||
<data name="Support_ValidationFailureContent" xml:space="preserve">
|
||||
<value>Please check the validation link in your email and try again.</value>
|
||||
</data>
|
||||
<data name="Support_ValidationFailureTitle" xml:space="preserve">
|
||||
<value>We were unable to validate your account.</value>
|
||||
</data>
|
||||
<data name="Support_ValidationSuccessContent" xml:space="preserve">
|
||||
<value>You will now be able to log in using your username and password.</value>
|
||||
</data>
|
||||
<data name="Support_ValidationSuccessTitle" xml:space="preserve">
|
||||
<value>You have successfully validated your account.</value>
|
||||
</data>
|
||||
<data name="Support_Week" xml:space="preserve">
|
||||
<value>Week</value>
|
||||
</data>
|
||||
|
|
|
@ -46,17 +46,15 @@
|
|||
<a href="mailto:@mu.Email">@mu.Email</a>
|
||||
</td>
|
||||
<td>
|
||||
@(mu.CreationDate == default(DateTime)
|
||||
? "Never"
|
||||
: mu.CreationDate.Humanize())
|
||||
@mu.CreationDate.Humanize()
|
||||
</td>
|
||||
<td>
|
||||
@(mu.LastLoginDate == default(DateTime)
|
||||
@(mu.LastLoginDate == DateTime.MinValue
|
||||
? "Never"
|
||||
: mu.LastLoginDate.Humanize())
|
||||
</td>
|
||||
<td>
|
||||
@(mu.LastLockoutDate == default(DateTime)
|
||||
@(mu.LastLockoutDate == DateTime.MinValue
|
||||
? "Never"
|
||||
: mu.LastLockoutDate.Humanize())
|
||||
</td>
|
||||
|
|
|
@ -73,23 +73,23 @@
|
|||
<Reference Include="Microsoft.AI.Agent.Intercept, Version=2.0.7.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.ApplicationInsights.Agent.Intercept.2.0.7\lib\net45\Microsoft.AI.Agent.Intercept.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.AI.DependencyCollector, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.ApplicationInsights.DependencyCollector.2.2.0\lib\net45\Microsoft.AI.DependencyCollector.dll</HintPath>
|
||||
<Reference Include="Microsoft.AI.DependencyCollector, Version=2.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.ApplicationInsights.DependencyCollector.2.3.0\lib\net45\Microsoft.AI.DependencyCollector.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.AI.PerfCounterCollector, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.ApplicationInsights.PerfCounterCollector.2.2.0\lib\net45\Microsoft.AI.PerfCounterCollector.dll</HintPath>
|
||||
<Reference Include="Microsoft.AI.PerfCounterCollector, Version=2.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.ApplicationInsights.PerfCounterCollector.2.3.0\lib\net45\Microsoft.AI.PerfCounterCollector.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.AI.ServerTelemetryChannel, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.2.2.0\lib\net45\Microsoft.AI.ServerTelemetryChannel.dll</HintPath>
|
||||
<Reference Include="Microsoft.AI.ServerTelemetryChannel, Version=2.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel.2.3.0\lib\net45\Microsoft.AI.ServerTelemetryChannel.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.AI.Web, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.ApplicationInsights.Web.2.2.0\lib\net45\Microsoft.AI.Web.dll</HintPath>
|
||||
<Reference Include="Microsoft.AI.Web, Version=2.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.ApplicationInsights.Web.2.3.0\lib\net45\Microsoft.AI.Web.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.AI.WindowsServer, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.ApplicationInsights.WindowsServer.2.2.0\lib\net45\Microsoft.AI.WindowsServer.dll</HintPath>
|
||||
<Reference Include="Microsoft.AI.WindowsServer, Version=2.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.ApplicationInsights.WindowsServer.2.3.0\lib\net45\Microsoft.AI.WindowsServer.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.ApplicationInsights, Version=2.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.ApplicationInsights.2.2.0\lib\net46\Microsoft.ApplicationInsights.dll</HintPath>
|
||||
<Reference Include="Microsoft.ApplicationInsights, Version=2.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.ApplicationInsights.2.3.0\lib\net46\Microsoft.ApplicationInsights.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=1.0.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.3\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll</HintPath>
|
||||
|
@ -109,10 +109,10 @@
|
|||
<HintPath>..\packages\MongoDB.Driver.Core.2.4.3\lib\net45\MongoDB.Driver.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.10.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<HintPath>..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="OneSignal.CSharp.SDK, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\OneSignal.CSharp.SDK.0.9\lib\net45\OneSignal.CSharp.SDK.dll</HintPath>
|
||||
<HintPath>..\packages\OneSignal.CSharp.SDK.0.10\lib\net45\OneSignal.CSharp.SDK.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="RestSharp, Version=105.2.3.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\RestSharp.105.2.3\lib\net46\RestSharp.dll</HintPath>
|
||||
|
@ -195,6 +195,7 @@
|
|||
<Compile Include="Areas\admin\Controllers\metaController.cs" />
|
||||
<Compile Include="Areas\admin\Models\ViewModel\MetaListing.cs" />
|
||||
<Compile Include="Code\AiHandleErrorAttribute.cs" />
|
||||
<Compile Include="Code\Base32Encoding.cs" />
|
||||
<Compile Include="Code\CustomContentTypeAttribute.cs" />
|
||||
<Compile Include="Code\DateTimeModelBinder.cs" />
|
||||
<Compile Include="Code\DisplayHelpers.cs" />
|
||||
|
@ -202,11 +203,14 @@
|
|||
<Compile Include="App_Start\RouteConfig.cs" />
|
||||
<Compile Include="Areas\admin\adminAreaRegistration.cs" />
|
||||
<Compile Include="Areas\admin\Controllers\usersController.cs" />
|
||||
<Compile Include="Code\EmailManager\EmailManager.cs" />
|
||||
<Compile Include="Code\EmailManager\RegistrationEmail.cs" />
|
||||
<Compile Include="Code\MvcIntrinsics.cs" />
|
||||
<Compile Include="Code\OneSignalHelper.cs" />
|
||||
<Compile Include="Code\Options\Locale.cs" />
|
||||
<Compile Include="Code\Options\Theme.cs" />
|
||||
<Compile Include="Code\OutputCachePushAttribute.cs" />
|
||||
<Compile Include="Controllers\AccountController.cs" />
|
||||
<Compile Include="Controllers\ApiController.cs" />
|
||||
<Compile Include="Controllers\BaseController.cs" />
|
||||
<Compile Include="Controllers\FrontController.cs" />
|
||||
|
@ -391,10 +395,10 @@
|
|||
<Content Include="Views\shared\_default.cshtml" />
|
||||
<Content Include="Views\front\EditBuild.cshtml" />
|
||||
<Content Include="Views\shared\DisplayTemplates\Enumeration.cshtml" />
|
||||
<Content Include="Views\support\register.cshtml" />
|
||||
<Content Include="Views\support\login.cshtml" />
|
||||
<Content Include="Views\support\password.cshtml" />
|
||||
<Content Include="Views\support\thanks_register.cshtml" />
|
||||
<Content Include="Views\account\register.cshtml" />
|
||||
<Content Include="Views\account\login.cshtml" />
|
||||
<Content Include="Views\account\password.cshtml" />
|
||||
<Content Include="Views\account\registerthanks.cshtml" />
|
||||
<Content Include="Views\support\rss.cshtml" />
|
||||
<Content Include="Views\support\sitemap.cshtml" />
|
||||
<Content Include="smtp.config">
|
||||
|
@ -416,6 +420,8 @@
|
|||
<Content Include="Views\front\AddBulk.cshtml" />
|
||||
<Content Include="Scripts\trumbowyg\plugins\colors\ui\sass\trumbowyg.colors.scss" />
|
||||
<Content Include="Scripts\trumbowyg\ui\sass\trumbowyg.scss" />
|
||||
<Content Include="Views\account\validate-success.cshtml" />
|
||||
<Content Include="Views\account\validate-failure.cshtml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<ItemGroup>
|
||||
|
|
2
BuildFeed/BuildFeed.csproj.DotSettings
Normal file
2
BuildFeed/BuildFeed.csproj.DotSettings
Normal file
|
@ -0,0 +1,2 @@
|
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=code_005Cemailmanager/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
133
BuildFeed/Code/Base32Encoding.cs
Normal file
133
BuildFeed/Code/Base32Encoding.cs
Normal file
|
@ -0,0 +1,133 @@
|
|||
using System;
|
||||
|
||||
namespace BuildFeed.Code
|
||||
{
|
||||
/* Based off http://stackoverflow.com/a/7135008 */
|
||||
public class Base32Encoding
|
||||
{
|
||||
public static byte[] ToBytes(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
}
|
||||
|
||||
input = input.TrimEnd('='); //remove padding characters
|
||||
int byteCount = input.Length * 5 / 8; //this must be TRUNCATED
|
||||
var returnArray = new byte[byteCount];
|
||||
|
||||
byte curByte = 0,
|
||||
bitsRemaining = 8;
|
||||
|
||||
int arrayIndex = 0;
|
||||
|
||||
foreach (char c in input)
|
||||
{
|
||||
int cValue = CharToValue(c);
|
||||
|
||||
int mask;
|
||||
if (bitsRemaining > 5)
|
||||
{
|
||||
mask = cValue << (bitsRemaining - 5);
|
||||
curByte = (byte)(curByte | mask);
|
||||
bitsRemaining -= 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
mask = cValue >> (5 - bitsRemaining);
|
||||
curByte = (byte)(curByte | mask);
|
||||
returnArray[arrayIndex++] = curByte;
|
||||
curByte = (byte)(cValue << (3 + bitsRemaining));
|
||||
bitsRemaining += 3;
|
||||
}
|
||||
}
|
||||
|
||||
//if we didn't end with a full byte
|
||||
if (arrayIndex != byteCount)
|
||||
{
|
||||
returnArray[arrayIndex] = curByte;
|
||||
}
|
||||
|
||||
return returnArray;
|
||||
}
|
||||
|
||||
public static string ToString(byte[] input)
|
||||
{
|
||||
if (input == null || input.Length == 0)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(input));
|
||||
}
|
||||
|
||||
int charCount = (int)Math.Ceiling(input.Length / 5d) * 8;
|
||||
var returnArray = new char[charCount];
|
||||
|
||||
byte nextChar = 0,
|
||||
bitsRemaining = 5;
|
||||
int arrayIndex = 0;
|
||||
|
||||
foreach (byte b in input)
|
||||
{
|
||||
nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining)));
|
||||
returnArray[arrayIndex++] = ValueToChar(nextChar);
|
||||
|
||||
if (bitsRemaining < 4)
|
||||
{
|
||||
nextChar = (byte)((b >> (3 - bitsRemaining)) & 31);
|
||||
returnArray[arrayIndex++] = ValueToChar(nextChar);
|
||||
bitsRemaining += 5;
|
||||
}
|
||||
|
||||
bitsRemaining -= 3;
|
||||
nextChar = (byte)((b << bitsRemaining) & 31);
|
||||
}
|
||||
|
||||
//if we didn't end with a full char
|
||||
if (arrayIndex != charCount)
|
||||
{
|
||||
returnArray[arrayIndex++] = ValueToChar(nextChar);
|
||||
while (arrayIndex != charCount)
|
||||
returnArray[arrayIndex++] = '='; //padding
|
||||
}
|
||||
|
||||
return new string(returnArray);
|
||||
}
|
||||
|
||||
private static int CharToValue(char c)
|
||||
{
|
||||
int value = c;
|
||||
|
||||
//65-90 == uppercase letters
|
||||
if (value < 91 && value > 64)
|
||||
{
|
||||
return value - 65;
|
||||
}
|
||||
//50-55 == numbers 2-7
|
||||
if (value < 56 && value > 49)
|
||||
{
|
||||
return value - 24;
|
||||
}
|
||||
//97-122 == lowercase letters
|
||||
if (value < 123 && value > 96)
|
||||
{
|
||||
return value - 97;
|
||||
}
|
||||
|
||||
throw new ArgumentException("Character is not a Base32 character.", nameof(c));
|
||||
}
|
||||
|
||||
private static char ValueToChar(byte b)
|
||||
{
|
||||
if (b < 26)
|
||||
{
|
||||
return (char)(b + 65);
|
||||
}
|
||||
|
||||
if (b < 32)
|
||||
{
|
||||
return (char)(b + 24);
|
||||
}
|
||||
|
||||
throw new ArgumentException("Byte is not a value Base32 value.", nameof(b));
|
||||
}
|
||||
}
|
||||
}
|
7
BuildFeed/Code/EmailManager/EmailManager.cs
Normal file
7
BuildFeed/Code/EmailManager/EmailManager.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace BuildFeed.Code
|
||||
{
|
||||
public static partial class EmailManager
|
||||
{
|
||||
private const string EMAIL_FROM = "thomas@buildfeed.net";
|
||||
}
|
||||
}
|
24
BuildFeed/Code/EmailManager/RegistrationEmail.cs
Normal file
24
BuildFeed/Code/EmailManager/RegistrationEmail.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
using System.Net.Mail;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Security;
|
||||
using BuildFeed.Local;
|
||||
|
||||
namespace BuildFeed.Code
|
||||
{
|
||||
public static partial class EmailManager
|
||||
{
|
||||
public static async Task SendRegistrationEmail(MembershipUser mu, string validationLink)
|
||||
{
|
||||
using (MailMessage mm = new MailMessage(EMAIL_FROM, mu.Email))
|
||||
{
|
||||
mm.Subject = string.Format(VariantTerms.Email_Registration_Subject, InvariantTerms.SiteName);
|
||||
mm.Body = string.Format(VariantTerms.Email_Registration_Body, InvariantTerms.SiteName, validationLink);
|
||||
|
||||
using (SmtpClient sc = new SmtpClient())
|
||||
{
|
||||
await sc.SendMailAsync(mm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
156
BuildFeed/Controllers/AccountController.cs
Normal file
156
BuildFeed/Controllers/AccountController.cs
Normal file
|
@ -0,0 +1,156 @@
|
|||
using System;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using System.Web.Security;
|
||||
using BuildFeed.Code;
|
||||
using BuildFeed.Local;
|
||||
using BuildFeed.Model.View;
|
||||
using MongoAuth;
|
||||
|
||||
namespace BuildFeed.Controllers
|
||||
{
|
||||
public class AccountController : BaseController
|
||||
{
|
||||
[Route("login/")]
|
||||
public ActionResult Login() => View();
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[Route("login/")]
|
||||
public ActionResult Login(LoginUser ru)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
bool isAuthenticated = Membership.ValidateUser(ru.UserName, ru.Password);
|
||||
|
||||
if (isAuthenticated)
|
||||
{
|
||||
int expiryLength = ru.RememberMe
|
||||
? 129600
|
||||
: 60;
|
||||
|
||||
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(ru.UserName, true, expiryLength);
|
||||
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
|
||||
HttpCookie cookieTicket = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket)
|
||||
{
|
||||
Expires = DateTime.Now.AddMinutes(expiryLength),
|
||||
Path = FormsAuthentication.FormsCookiePath
|
||||
};
|
||||
Response.Cookies.Add(cookieTicket);
|
||||
|
||||
string returnUrl = string.IsNullOrEmpty(Request.QueryString["ReturnUrl"])
|
||||
? "/"
|
||||
: Request.QueryString["ReturnUrl"];
|
||||
|
||||
return Redirect(returnUrl);
|
||||
}
|
||||
}
|
||||
|
||||
ViewData["ErrorMessage"] = "The username and password are not valid.";
|
||||
return View(ru);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("password/")]
|
||||
public ActionResult Password() => View();
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[Authorize]
|
||||
[Route("password/")]
|
||||
public ActionResult Password(ChangePassword cp)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
MembershipUser user = Membership.GetUser();
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
bool success = user.ChangePassword(cp.OldPassword, cp.NewPassword);
|
||||
|
||||
if (success)
|
||||
{
|
||||
return Redirect("/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ViewData["ErrorMessage"] = VariantTerms.Support_Error_ChangingPasswordFail;
|
||||
return View(cp);
|
||||
}
|
||||
|
||||
[Route("logout/")]
|
||||
public ActionResult Logout()
|
||||
{
|
||||
FormsAuthentication.SignOut();
|
||||
return Redirect("/");
|
||||
}
|
||||
|
||||
[Route("register/")]
|
||||
public ActionResult Register() => View();
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
[Route("register/")]
|
||||
public async Task<ActionResult> Register(RegistrationUser ru)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
MembershipCreateStatus status;
|
||||
MembershipUser mu = Membership.CreateUser(ru.UserName, ru.Password, ru.EmailAddress, "{IGNORE}", "{IGNORE}", false, out status);
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case MembershipCreateStatus.Success:
|
||||
{
|
||||
Roles.AddUserToRole(mu.UserName, "Users");
|
||||
|
||||
MongoMembershipProvider provider = (MongoMembershipProvider)Membership.Provider;
|
||||
Guid id = (Guid)mu.ProviderUserKey;
|
||||
string hash = (await provider.GenerateValidationHash(id)).ToLower();
|
||||
|
||||
string fullUrl = Request.Url?.GetLeftPart(UriPartial.Authority) + Url.Action("Validate",
|
||||
"Account",
|
||||
new
|
||||
{
|
||||
id,
|
||||
hash
|
||||
});
|
||||
await EmailManager.SendRegistrationEmail(mu, fullUrl);
|
||||
return RedirectToAction(nameof(RegisterThanks));
|
||||
}
|
||||
case MembershipCreateStatus.InvalidPassword:
|
||||
ViewData["ErrorMessage"] = VariantTerms.Support_Error_InvalidPassword;
|
||||
break;
|
||||
case MembershipCreateStatus.DuplicateEmail:
|
||||
ViewData["ErrorMessage"] = VariantTerms.Support_Error_DuplicateEmail;
|
||||
break;
|
||||
case MembershipCreateStatus.DuplicateUserName:
|
||||
ViewData["ErrorMessage"] = VariantTerms.Support_Error_DuplicateUserName;
|
||||
break;
|
||||
default:
|
||||
ViewData["ErrorMessage"] = VariantTerms.Support_Error_UnknownError;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return View(ru);
|
||||
}
|
||||
|
||||
[Route("register/thanks/")]
|
||||
public ActionResult RegisterThanks() => View();
|
||||
|
||||
[Route("validate/{id:guid}/{hash}/")]
|
||||
public async Task<ActionResult> Validate(Guid id, string hash)
|
||||
{
|
||||
MongoMembershipProvider provider = (MongoMembershipProvider)Membership.Provider;
|
||||
bool success = await provider.ValidateUserFromHash(id, hash);
|
||||
|
||||
return View(success
|
||||
? "validate-success"
|
||||
: "validate-failure");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@
|
|||
using BuildFeed.Model.View;
|
||||
using OneSignal.CSharp.SDK;
|
||||
|
||||
#pragma warning disable SG0016 // Controller method is vulnerable to CSRF - Not relevant for API
|
||||
namespace BuildFeed.Controllers
|
||||
{
|
||||
public class ApiController : System.Web.Http.ApiController
|
||||
|
@ -70,7 +71,7 @@ public async Task<bool> AddWin10Builds(NewBuildPost apiModel)
|
|||
{
|
||||
return false;
|
||||
}
|
||||
if (Membership.ValidateUser(apiModel.Username, apiModel.Password))
|
||||
if (Membership.ValidateUser(apiModel.Username, apiModel.Password) && (Roles.IsUserInRole(apiModel.Username, "Editors") || Roles.IsUserInRole(apiModel.Username, "Administrators")))
|
||||
{
|
||||
IEnumerable<Build> builds = apiModel.NewBuilds.Select(nb => new Build
|
||||
{
|
||||
|
|
|
@ -378,7 +378,7 @@ public async Task<ActionResult> ViewVersionPage(uint major, uint minor, int page
|
|||
}
|
||||
|
||||
[Route("add/")]
|
||||
[Authorize]
|
||||
[Authorize(Roles = "Administrators,Editors")]
|
||||
public ActionResult AddBuild()
|
||||
{
|
||||
Build b = new Build
|
||||
|
@ -389,7 +389,8 @@ public ActionResult AddBuild()
|
|||
}
|
||||
|
||||
[Route("add/")]
|
||||
[Authorize]
|
||||
[ValidateAntiForgeryToken]
|
||||
[Authorize(Roles = "Administrators,Editors")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> AddBuild(Build build)
|
||||
{
|
||||
|
@ -429,14 +430,15 @@ public async Task<ActionResult> AddBuild(Build build)
|
|||
}
|
||||
|
||||
[Route("bulk/")]
|
||||
[Authorize]
|
||||
[Authorize(Roles = "Administrators")]
|
||||
public ActionResult AddBulk()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[Route("bulk/")]
|
||||
[Authorize]
|
||||
[ValidateAntiForgeryToken]
|
||||
[Authorize(Roles = "Administrators")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> AddBulk(FormCollection values)
|
||||
{
|
||||
|
@ -509,7 +511,7 @@ public async Task<ActionResult> AddBulk(FormCollection values)
|
|||
}
|
||||
|
||||
[Route("edit/{id}/")]
|
||||
[Authorize]
|
||||
[Authorize(Roles = "Administrators,Editors")]
|
||||
public async Task<ActionResult> EditBuild(Guid id)
|
||||
{
|
||||
Build b = await _bModel.SelectById(id);
|
||||
|
@ -517,7 +519,8 @@ public async Task<ActionResult> EditBuild(Guid id)
|
|||
}
|
||||
|
||||
[Route("edit/{id}/")]
|
||||
[Authorize]
|
||||
[ValidateAntiForgeryToken]
|
||||
[Authorize(Roles = "Administrators,Editors")]
|
||||
[HttpPost]
|
||||
public async Task<ActionResult> EditBuild(Guid id, Build build)
|
||||
{
|
||||
|
|
|
@ -2,10 +2,8 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using System.Web.Routing;
|
||||
using System.Web.Security;
|
||||
using System.Xml.Linq;
|
||||
using BuildFeed.Code;
|
||||
using BuildFeed.Local;
|
||||
|
@ -23,116 +21,6 @@ public SupportController()
|
|||
_bModel = new BuildRepository();
|
||||
}
|
||||
|
||||
[Route("login/")]
|
||||
public ActionResult Login() => View();
|
||||
|
||||
[HttpPost]
|
||||
[Route("login/")]
|
||||
public ActionResult Login(LoginUser ru)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
bool isAuthenticated = Membership.ValidateUser(ru.UserName, ru.Password);
|
||||
|
||||
if (isAuthenticated)
|
||||
{
|
||||
int expiryLength = ru.RememberMe
|
||||
? 129600
|
||||
: 60;
|
||||
|
||||
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(ru.UserName, true, expiryLength);
|
||||
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
|
||||
HttpCookie cookieTicket = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket)
|
||||
{
|
||||
Expires = DateTime.Now.AddMinutes(expiryLength),
|
||||
Path = FormsAuthentication.FormsCookiePath
|
||||
};
|
||||
Response.Cookies.Add(cookieTicket);
|
||||
|
||||
string returnUrl = string.IsNullOrEmpty(Request.QueryString["ReturnUrl"])
|
||||
? "/"
|
||||
: Request.QueryString["ReturnUrl"];
|
||||
|
||||
return Redirect(returnUrl);
|
||||
}
|
||||
}
|
||||
|
||||
ViewData["ErrorMessage"] = "The username and password are not valid.";
|
||||
return View(ru);
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[Route("password/")]
|
||||
public ActionResult Password() => View();
|
||||
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
[Route("password/")]
|
||||
public ActionResult Password(ChangePassword cp)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
MembershipUser user = Membership.GetUser();
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
bool success = user.ChangePassword(cp.OldPassword, cp.NewPassword);
|
||||
|
||||
if (success)
|
||||
{
|
||||
return Redirect("/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ViewData["ErrorMessage"] = "There was an error changing your password.";
|
||||
return View(cp);
|
||||
}
|
||||
|
||||
[Route("logout/")]
|
||||
public ActionResult Logout()
|
||||
{
|
||||
FormsAuthentication.SignOut();
|
||||
return Redirect("/");
|
||||
}
|
||||
|
||||
[Route("register/")]
|
||||
public ActionResult Register() => View();
|
||||
|
||||
[HttpPost]
|
||||
[Route("register/")]
|
||||
public ActionResult Register(RegistrationUser ru)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
MembershipCreateStatus status;
|
||||
Membership.CreateUser(ru.UserName, ru.Password, ru.EmailAddress, "THIS WILL BE IGNORED", "I WILL BE IGNORED", false, out status);
|
||||
|
||||
switch (status)
|
||||
{
|
||||
case MembershipCreateStatus.Success:
|
||||
return RedirectToAction("thanks_register");
|
||||
case MembershipCreateStatus.InvalidPassword:
|
||||
ViewData["ErrorMessage"] = "The password is invalid.";
|
||||
break;
|
||||
case MembershipCreateStatus.DuplicateEmail:
|
||||
ViewData["ErrorMessage"] = "A user account with this email address already exists.";
|
||||
break;
|
||||
case MembershipCreateStatus.DuplicateUserName:
|
||||
ViewData["ErrorMessage"] = "A user account with this user name already exists.";
|
||||
break;
|
||||
default:
|
||||
ViewData["ErrorMessage"] = "Unspecified error.";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return View(ru);
|
||||
}
|
||||
|
||||
[Route("register/thanks/")]
|
||||
public ActionResult thanks_register() => View();
|
||||
|
||||
[Route("rss")]
|
||||
public async Task<ActionResult> Rss()
|
||||
{
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using System.Web.Routing;
|
||||
using System.Web.Security;
|
||||
using BuildFeed.Code;
|
||||
using BuildFeed.Code.Options;
|
||||
using BuildFeed.Model;
|
||||
|
@ -30,6 +31,10 @@ protected void Application_Start()
|
|||
ModelBinders.Binders.Add(typeof(DateTime), db);
|
||||
ModelBinders.Binders.Add(typeof(DateTime?), db);
|
||||
|
||||
Roles.CreateRole("Administrators");
|
||||
Roles.CreateRole("Editors");
|
||||
Roles.CreateRole("Users");
|
||||
|
||||
MongoConfig.SetupIndexes();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@model BuildFeed.Model.View.LoginUser
|
||||
@using BuildFeed.Controllers
|
||||
@model BuildFeed.Model.View.LoginUser
|
||||
|
||||
@{
|
||||
ViewBag.Title = $"{VariantTerms.Support_Login} | {InvariantTerms.SiteName}";
|
||||
|
@ -57,9 +58,9 @@
|
|||
<div class="form-group">
|
||||
<div>
|
||||
<input type="submit" value="@VariantTerms.Support_Login" class="btn btn-primary" />  
|
||||
@Html.ActionLink(VariantTerms.Support_Register, "register", new
|
||||
@Html.ActionLink(VariantTerms.Support_Register, nameof(AccountController.Register), new
|
||||
{
|
||||
controller = "Support"
|
||||
controller = "Account"
|
||||
}, new
|
||||
{
|
||||
@class = "btn btn-default"
|
6
BuildFeed/Views/account/registerthanks.cshtml
Normal file
6
BuildFeed/Views/account/registerthanks.cshtml
Normal file
|
@ -0,0 +1,6 @@
|
|||
@{
|
||||
ViewBag.Title = $"{VariantTerms.Support_EmailValidationTitle} | {InvariantTerms.SiteName}";
|
||||
}
|
||||
|
||||
<h1>@VariantTerms.Support_EmailValidationTitle</h1>
|
||||
<p>@VariantTerms.Support_EmailValidationContent</p>
|
6
BuildFeed/Views/account/validate-failure.cshtml
Normal file
6
BuildFeed/Views/account/validate-failure.cshtml
Normal file
|
@ -0,0 +1,6 @@
|
|||
@{
|
||||
ViewBag.Title = $"{VariantTerms.Support_ValidationFailureTitle} | {InvariantTerms.SiteName}";
|
||||
}
|
||||
|
||||
<h1>@VariantTerms.Support_ValidationFailureTitle</h1>
|
||||
<p>@VariantTerms.Support_ValidationFailureContent</p>
|
6
BuildFeed/Views/account/validate-success.cshtml
Normal file
6
BuildFeed/Views/account/validate-success.cshtml
Normal file
|
@ -0,0 +1,6 @@
|
|||
@{
|
||||
ViewBag.Title = $"{VariantTerms.Support_ValidationSuccessTitle} | {InvariantTerms.SiteName}";
|
||||
}
|
||||
|
||||
<h1>@VariantTerms.Support_ValidationSuccessTitle</h1>
|
||||
<p>@VariantTerms.Support_ValidationSuccessContent</p>
|
|
@ -143,7 +143,7 @@
|
|||
<div class="addthis_sharing_toolbox"></div>
|
||||
<br />
|
||||
|
||||
@if (User.Identity.IsAuthenticated)
|
||||
@if (Roles.IsUserInRole("Editors") || Roles.IsUserInRole("Administrators"))
|
||||
{
|
||||
<h3>@VariantTerms.Front_EditorActions</h3>
|
||||
<p class="build-details-flex-value">
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
<i class="fa fa-lock fa-fw"></i> @VariantTerms.Front_Private</span>
|
||||
</p>
|
||||
}
|
||||
@if (User.Identity.IsAuthenticated)
|
||||
@if (Roles.IsUserInRole("Editors") || Roles.IsUserInRole("Administrators"))
|
||||
{
|
||||
<p>
|
||||
<a href="@Url.Action(nameof(FrontController.EditBuild), new
|
||||
|
|
|
@ -78,7 +78,7 @@
|
|||
<i class="fa fa-lock fa-fw"></i> @VariantTerms.Front_Private</span>
|
||||
</p>
|
||||
}
|
||||
@if (User.Identity.IsAuthenticated)
|
||||
@if (Roles.IsUserInRole("Editors") || Roles.IsUserInRole("Administrators"))
|
||||
{
|
||||
<p>
|
||||
<a href="@Url.Action(nameof(FrontController.EditBuild), new
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
<i class="fa fa-lock fa-fw"></i> @VariantTerms.Front_Private</span>
|
||||
</p>
|
||||
}
|
||||
@if (User.Identity.IsAuthenticated)
|
||||
@if (Roles.IsUserInRole("Editors") || Roles.IsUserInRole("Administrators"))
|
||||
{
|
||||
<p>
|
||||
<a href="@Url.Action(nameof(FrontController.EditBuild), new
|
||||
|
|
|
@ -74,7 +74,7 @@
|
|||
<i class="fa fa-lock fa-fw"></i> @VariantTerms.Front_Private</span>
|
||||
</p>
|
||||
}
|
||||
@if (User.Identity.IsAuthenticated)
|
||||
@if (Roles.IsUserInRole("Editors") || Roles.IsUserInRole("Administrators"))
|
||||
{
|
||||
<p>
|
||||
<a href="@Url.Action(nameof(FrontController.EditBuild), new
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
<i class="fa fa-lock fa-fw"></i> @VariantTerms.Front_Private</span>
|
||||
</p>
|
||||
}
|
||||
@if (User.Identity.IsAuthenticated)
|
||||
@if (Roles.IsUserInRole("Editors") || Roles.IsUserInRole("Administrators"))
|
||||
{
|
||||
<p>
|
||||
<a href="@Url.Action(nameof(FrontController.EditBuild), new
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
Response.PushPromise(VirtualPathUtility.ToAbsolute(((Theme)ViewBag.Theme).CssPath));
|
||||
if (isRtl)
|
||||
{
|
||||
Response.PushPromise("/res/css/rtl.css");
|
||||
Response.PushPromise("/res/css/rtl.css");
|
||||
}
|
||||
|
||||
Response.PushPromise("/res/ts/bfs.js");
|
||||
|
@ -18,268 +18,282 @@
|
|||
? "rtl"
|
||||
: "ltr")" lang="@CultureInfo.CurrentUICulture.TwoLetterISOLanguageName">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>@ViewBag.Title</title>
|
||||
<link href="/res/css/default.css" rel="stylesheet" type="text/css" />
|
||||
<link href="@(VirtualPathUtility.ToAbsolute(((Theme)ViewBag.Theme).CssPath))" rel="stylesheet" type="text/css" />
|
||||
@if (isRtl)
|
||||
{
|
||||
<link href="/res/css/rtl.css" rel="stylesheet" type="text/css" />
|
||||
}
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,700" rel="stylesheet" type="text/css" />
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" type="text/css" integrity="sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0=" crossorigin="anonymous" />
|
||||
<link rel="manifest" href="~/manifest.json">
|
||||
<link rel="shortcut icon" href="~/favicon.ico" />
|
||||
<link rel="icon" href="~/favicon.ico" />
|
||||
<link rel="canonical" href="@Url.Action()" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="@InvariantTerms.SiteName" />
|
||||
<meta property="og:url" content="@Url.Action()" />
|
||||
<meta name="theme-color" content="#373736">
|
||||
<meta name="application-name" content="@InvariantTerms.SiteName" />
|
||||
<meta name="twitter:site" content="@("@")buildfeed">
|
||||
@RenderSection("head", false)
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="format-detection" content="telephone=no" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>@ViewBag.Title</title>
|
||||
<link href="/res/css/default.css" rel="stylesheet" type="text/css" />
|
||||
<link href="@(VirtualPathUtility.ToAbsolute(((Theme)ViewBag.Theme).CssPath))" rel="stylesheet" type="text/css" />
|
||||
@if (isRtl)
|
||||
{
|
||||
<link href="/res/css/rtl.css" rel="stylesheet" type="text/css" />
|
||||
}
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,700" rel="stylesheet" type="text/css" />
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" type="text/css" integrity="sha256-eZrrJcwDc/3uDhsdt61sL2oOBY362qM3lon1gyExkL0=" crossorigin="anonymous" />
|
||||
<link rel="manifest" href="~/manifest.json">
|
||||
<link rel="shortcut icon" href="~/favicon.ico" />
|
||||
<link rel="icon" href="~/favicon.ico" />
|
||||
<link rel="canonical" href="@Url.Action()" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:site_name" content="@InvariantTerms.SiteName" />
|
||||
<meta property="og:url" content="@Url.Action()" />
|
||||
<meta name="theme-color" content="#373736">
|
||||
<meta name="application-name" content="@InvariantTerms.SiteName" />
|
||||
<meta name="twitter:site" content="@("@")buildfeed">
|
||||
@RenderSection("head", false)
|
||||
|
||||
<script src="https://cdn.onesignal.com/sdks/OneSignalSDK.js" async></script>
|
||||
<script>
|
||||
var OneSignal = window.OneSignal || [];
|
||||
OneSignal.push(["init", {
|
||||
appId: "94384f83-dced-4d66-a88c-c2b6e3cdfbaa",
|
||||
safari_web_id: "web.onesignal.auto.4cc30974-98f9-47ba-8e02-4635d2d477f2",
|
||||
persistNotification: false,
|
||||
allowLocalhostAsSecureOrigin: true,
|
||||
autoRegister: true,
|
||||
notifyButton: {
|
||||
enable: false
|
||||
}
|
||||
}]);
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
var appInsights = window.appInsights || function (config) {
|
||||
function i(config) { t[config] = function () { var i = arguments; t.queue.push(function () { t[config].apply(t, i) }) } } var t = { config: config }, u = document, e = window, o = "script", s = "AuthenticatedUserContext", h = "start", c = "stop", l = "Track", a = l + "Event", v = l + "Page", y = u.createElement(o), r, f; y.src = config.url || "https://az416426.vo.msecnd.net/scripts/a/ai.0.js"; u.getElementsByTagName(o)[0].parentNode.appendChild(y); try { t.cookie = u.cookie } catch (p) { } for (t.queue = [], t.version = "1.0", r = ["Event", "Exception", "Metric", "PageView", "Trace", "Dependency"]; r.length;)i("track" + r.pop()); return i("set" + s), i("clear" + s), i(h + a), i(c + a), i(h + v), i(c + v), i("flush"), config.disableExceptionTracking || (r = "onerror", i("_" + r), f = e[r], e[r] = function (config, i, u, e, o) { var s = f && f(config, i, u, e, o); return s !== !0 && t["_" + r](config, i, u, e, o), s }), t
|
||||
} ({
|
||||
instrumentationKey: "4632419f-7a2f-4ab5-8374-34384b650f42"
|
||||
});
|
||||
<script src="https://cdn.onesignal.com/sdks/OneSignalSDK.js" async></script>
|
||||
<script>
|
||||
var OneSignal = window.OneSignal || [];
|
||||
OneSignal.push(["init", {
|
||||
appId: "94384f83-dced-4d66-a88c-c2b6e3cdfbaa",
|
||||
safari_web_id: "web.onesignal.auto.4cc30974-98f9-47ba-8e02-4635d2d477f2",
|
||||
persistNotification: false,
|
||||
allowLocalhostAsSecureOrigin: true,
|
||||
autoRegister: true,
|
||||
notifyButton: {
|
||||
enable: false
|
||||
}
|
||||
}]);
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
var appInsights = window.appInsights || function (config) {
|
||||
function i(config) { t[config] = function () { var i = arguments; t.queue.push(function () { t[config].apply(t, i) }) } } var t = { config: config }, u = document, e = window, o = "script", s = "AuthenticatedUserContext", h = "start", c = "stop", l = "Track", a = l + "Event", v = l + "Page", y = u.createElement(o), r, f; y.src = config.url || "https://az416426.vo.msecnd.net/scripts/a/ai.0.js"; u.getElementsByTagName(o)[0].parentNode.appendChild(y); try { t.cookie = u.cookie } catch (p) { } for (t.queue = [], t.version = "1.0", r = ["Event", "Exception", "Metric", "PageView", "Trace", "Dependency"]; r.length;)i("track" + r.pop()); return i("set" + s), i("clear" + s), i(h + a), i(c + a), i(h + v), i(c + v), i("flush"), config.disableExceptionTracking || (r = "onerror", i("_" + r), f = e[r], e[r] = function (config, i, u, e, o) { var s = f && f(config, i, u, e, o); return s !== !0 && t["_" + r](config, i, u, e, o), s }), t
|
||||
}({
|
||||
instrumentationKey: "4632419f-7a2f-4ab5-8374-34384b650f42"
|
||||
});
|
||||
|
||||
window.appInsights = appInsights;
|
||||
appInsights.trackPageView();
|
||||
</script>
|
||||
window.appInsights = appInsights;
|
||||
appInsights.trackPageView();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
(function (i, s, o, g, r, a, m) {
|
||||
i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
|
||||
<script>
|
||||
(function (i, s, o, g, r, a, m) {
|
||||
i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
|
||||
(i[r].q = i[r].q || []).push(arguments);
|
||||
}, i[r].l = 1 * new Date(); a = s.createElement(o),
|
||||
}, i[r].l = 1 * new Date(); a = s.createElement(o),
|
||||
m = s.getElementsByTagName(o)[0]; a.async = 1; a.src = g; m.parentNode.insertBefore(a, m);
|
||||
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
|
||||
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
|
||||
|
||||
ga('create', 'UA-55417692-1', 'auto');
|
||||
ga('require', 'displayfeatures');
|
||||
ga('require', 'linkid', 'linkid.js');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
<header id="page-header">
|
||||
<div class="container">
|
||||
<h1>
|
||||
ga('create', 'UA-55417692-1', 'auto');
|
||||
ga('require', 'displayfeatures');
|
||||
ga('require', 'linkid', 'linkid.js');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
<header id="page-header">
|
||||
<div class="container">
|
||||
<h1>
|
||||
@Html.ActionLink(InvariantTerms.SiteName, nameof(FrontController.Index), new
|
||||
{
|
||||
controller = "Front",
|
||||
area = ""
|
||||
})
|
||||
</h1>
|
||||
</div>
|
||||
</header>
|
||||
<nav id="page-navigation" role="navigation">
|
||||
<div class="container">
|
||||
<button id="page-navigation-toggle">
|
||||
{
|
||||
controller = "Front",
|
||||
area = ""
|
||||
})
|
||||
</h1>
|
||||
</div>
|
||||
</header>
|
||||
<nav id="page-navigation" role="navigation">
|
||||
<div class="container">
|
||||
<button id="page-navigation-toggle">
|
||||
<i class="fa fa-bars"></i> @VariantTerms.Common_ToggleNavigation
|
||||
</button>
|
||||
<ul id="page-navigation-links">
|
||||
</button>
|
||||
<ul id="page-navigation-links">
|
||||
@if (!User.Identity.IsAuthenticated)
|
||||
{
|
||||
<li>
|
||||
<a href="@Url.Action(nameof(SupportController.Login), new
|
||||
<li>
|
||||
<a href="@Url.Action(nameof(AccountController.Login), new
|
||||
{
|
||||
controller = "Support",
|
||||
controller = "Account",
|
||||
area = ""
|
||||
})" title="@VariantTerms.Common_LogIn">
|
||||
<i class="fa fa-fw fa-user"></i> @VariantTerms.Common_LogIn
|
||||
</a>
|
||||
</li>
|
||||
<i class="fa fa-fw fa-user"></i> @VariantTerms.Common_LogIn
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Roles.IsUserInRole("Administrators"))
|
||||
{
|
||||
<li>
|
||||
<a href="@Url.Action("index", new
|
||||
if (Roles.IsUserInRole("Administrators"))
|
||||
{
|
||||
<li>
|
||||
<a href="@Url.Action("index", new
|
||||
{
|
||||
controller = "base",
|
||||
area = "admin"
|
||||
})" title="@VariantTerms.Common_Admin">
|
||||
<i class="fa fa-fw fa-cogs"></i> @VariantTerms.Common_Admin
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
<li>
|
||||
<a href="@Url.Action(nameof(FrontController.AddBuild), new
|
||||
{
|
||||
<i class="fa fa-fw fa-cogs"></i> @VariantTerms.Common_Admin
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
if (Roles.IsUserInRole("Administrators") || Roles.IsUserInRole("Editors"))
|
||||
{
|
||||
<li>
|
||||
<a href="@Url.Action(nameof(FrontController.AddBuild), new
|
||||
{
|
||||
controller = "Front",
|
||||
area = ""
|
||||
})" title="@VariantTerms.Common_AddBuild">
|
||||
})" title="@VariantTerms.Common_AddBuild">
|
||||
<i class="fa fa-fw fa-plus-square"></i> @VariantTerms.Common_AddBuild
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@Url.Action(nameof(FrontController.AddBulk), new
|
||||
{
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@Url.Action(nameof(FrontController.AddBulk), new
|
||||
{
|
||||
controller = "Front",
|
||||
area = ""
|
||||
})" title="@VariantTerms.Common_AddBulk">
|
||||
})" title="@VariantTerms.Common_AddBulk">
|
||||
<i class="fa fa-fw fa-database"></i> @VariantTerms.Common_AddBulk
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@Url.Action(nameof(SupportController.Logout), new
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
<li>
|
||||
<a href="@Url.Action(nameof(AccountController.Logout), new
|
||||
{
|
||||
controller = "Support",
|
||||
controller = "Account",
|
||||
area = ""
|
||||
})" title="@VariantTerms.Common_LogOut">
|
||||
<i class="fa fa-fw fa-user"></i> @VariantTerms.Common_LogOut
|
||||
</a>
|
||||
</li>
|
||||
<i class="fa fa-fw fa-user"></i> @VariantTerms.Common_LogOut
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@Url.Action(nameof(AccountController.Password), new
|
||||
{
|
||||
controller = "Account",
|
||||
area = ""
|
||||
})" title="@VariantTerms.Common_ChangePassword">
|
||||
<i class="fa fa-fw fa-key"></i> @VariantTerms.Common_ChangePassword
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
<li>
|
||||
<a href="#" id="page-navigation-search" title="@VariantTerms.Search_Title">
|
||||
<i class="fa fa-fw fa-search"></i> @VariantTerms.Search_Title
|
||||
</a>
|
||||
<a href="#" id="page-navigation-search" title="@VariantTerms.Search_Title">
|
||||
<i class="fa fa-fw fa-search"></i> @VariantTerms.Search_Title
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@Url.Action(nameof(SupportController.Rss), new
|
||||
<a href="@Url.Action(nameof(SupportController.Rss), new
|
||||
{
|
||||
controller = "Support",
|
||||
area = ""
|
||||
})" title="@VariantTerms.Common_RssFeeds">
|
||||
<i class="fa fa-fw fa-rss"></i> @VariantTerms.Common_RssFeeds
|
||||
</a>
|
||||
<i class="fa fa-fw fa-rss"></i> @VariantTerms.Common_RssFeeds
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://twitter.com/buildfeed" title="@VariantTerms.Common_Twitter" target="_blank" rel="noopener">
|
||||
<i class="fa fa-fw fa-twitter"></i> @VariantTerms.Common_Twitter
|
||||
</a>
|
||||
<a href="https://twitter.com/buildfeed" title="@VariantTerms.Common_Twitter" target="_blank" rel="noopener">
|
||||
<i class="fa fa-fw fa-twitter"></i> @VariantTerms.Common_Twitter
|
||||
</a>
|
||||
</li>
|
||||
<li class="dropdown-parent">
|
||||
<a href="#">
|
||||
<i class="fa fa-gear"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li id="settings-theme-menu" class="dropdown-menu-block">
|
||||
<h4>@VariantTerms.Common_NavigationTheme</h4>
|
||||
<ul>
|
||||
@foreach (Theme item in Theme.AvailableThemes)
|
||||
{
|
||||
<li>
|
||||
<a href="#" data-theme="@item.CookieValue">@item.DisplayName</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</li>
|
||||
<li id="settings-lang-menu" class="dropdown-menu-block">
|
||||
<h4>@VariantTerms.Common_NavigationLanguage</h4>
|
||||
<ul>
|
||||
@foreach (Locale locale in Locale.AvailableLocales)
|
||||
{
|
||||
<li>
|
||||
<a href="#" data-lang="@locale.LocaleId" dir="@(locale.Info.TextInfo.IsRightToLeft
|
||||
<a href="#">
|
||||
<i class="fa fa-gear"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li id="settings-theme-menu" class="dropdown-menu-block">
|
||||
<h4>@VariantTerms.Common_NavigationTheme</h4>
|
||||
<ul>
|
||||
@foreach (Theme item in Theme.AvailableThemes)
|
||||
{
|
||||
<li>
|
||||
<a href="#" data-theme="@item.CookieValue">@item.DisplayName</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</li>
|
||||
<li id="settings-lang-menu" class="dropdown-menu-block">
|
||||
<h4>@VariantTerms.Common_NavigationLanguage</h4>
|
||||
<ul>
|
||||
@foreach (Locale locale in Locale.AvailableLocales)
|
||||
{
|
||||
<li>
|
||||
<a href="#" data-lang="@locale.LocaleId" dir="@(locale.Info.TextInfo.IsRightToLeft
|
||||
? "rtl"
|
||||
: "ltr")">
|
||||
@locale.DisplayName</a>
|
||||
</li>
|
||||
}
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@locale.DisplayName
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<article id="page-content">
|
||||
<div class="container">
|
||||
@RenderBody()
|
||||
</div>
|
||||
</article>
|
||||
<footer id="page-footer">
|
||||
<div class="container">
|
||||
<div class="footer-flex">
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<article id="page-content">
|
||||
<div class="container">
|
||||
@RenderBody()
|
||||
</div>
|
||||
</article>
|
||||
<footer id="page-footer">
|
||||
<div class="container">
|
||||
<div class="footer-flex">
|
||||
<div class="footer-flex-item">
|
||||
<p>
|
||||
<a href="@Url.Action(nameof(SupportController.Sitemap), new
|
||||
<p>
|
||||
<a href="@Url.Action(nameof(SupportController.Sitemap), new
|
||||
{
|
||||
controller = "Support",
|
||||
area = ""
|
||||
})">
|
||||
@VariantTerms.Common_Sitemap</a>
|
||||
</p>
|
||||
<p>
|
||||
<i class="fa fa-language"></i> 
|
||||
@CultureInfo.CurrentUICulture.NativeName
|
||||
(<a href="@Url.Action(nameof(SupportController.Credits), new
|
||||
@VariantTerms.Common_Sitemap
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<i class="fa fa-language"></i> 
|
||||
@CultureInfo.CurrentUICulture.NativeName
|
||||
(<a href="@Url.Action(nameof(SupportController.Credits), new
|
||||
{
|
||||
controller = "Support",
|
||||
area = ""
|
||||
})">
|
||||
@VariantTerms.Common_Credits
|
||||
</a>)
|
||||
</p>
|
||||
@VariantTerms.Common_Credits
|
||||
</a>)
|
||||
</p>
|
||||
</div>
|
||||
<div class="footer-flex-item">
|
||||
<p>
|
||||
© 2013 - @DateTime.Now.Year.ToString(), <span dir="ltr">@InvariantTerms.SiteName</span>
|
||||
</p>
|
||||
<p>
|
||||
@Html.Raw(string.Format(VariantTerms.Common_DevelopedBy, $"<a href=\"https://twitter.com/tomhounsell\" target=\"_blank\" dir=\"ltr\" rel=\"noopener\">{InvariantTerms.DeveloperName}</a>"))
|
||||
</p>
|
||||
<p>
|
||||
@Html.Raw(string.Format(VariantTerms.Common_ContributeOn, "<a href=\"https://github.com/hounsell/BuildFeed\" target=\"_blank\" dir=\"ltr\" rel=\"noopener\"><i class=\"fa fa-github\"></i> GitHub</a>"))
|
||||
</p>
|
||||
<p>
|
||||
© 2013 - @DateTime.Now.Year.ToString(), <span dir="ltr">@InvariantTerms.SiteName</span>
|
||||
</p>
|
||||
<p>
|
||||
@Html.Raw(string.Format(VariantTerms.Common_DevelopedBy, $"<a href=\"https://twitter.com/tomhounsell\" target=\"_blank\" dir=\"ltr\" rel=\"noopener\">{InvariantTerms.DeveloperName}</a>"))
|
||||
</p>
|
||||
<p>
|
||||
@Html.Raw(string.Format(VariantTerms.Common_ContributeOn, "<a href=\"https://github.com/hounsell/BuildFeed\" target=\"_blank\" dir=\"ltr\" rel=\"noopener\"><i class=\"fa fa-github\"></i> GitHub</a>"))
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<div id="modal-search-overlay">
|
||||
<div id="modal-search">
|
||||
<h3>@VariantTerms.Search_BuildFeed</h3>
|
||||
<div id="modal-search-box">
|
||||
<div id="modal-search-overlay">
|
||||
<div id="modal-search">
|
||||
<h3>@VariantTerms.Search_BuildFeed</h3>
|
||||
<div id="modal-search-box">
|
||||
<input id="modal-search-input" type="text" placeholder="@VariantTerms.Search_TypePlaceholder" />
|
||||
<button id="modal-search-button">
|
||||
<i class="fa fa-search"></i>
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="modal-search-result"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="menu-open-overlay"></div>
|
||||
</div>
|
||||
<div id="modal-search-result"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="menu-open-overlay"></div>
|
||||
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jsrender/1.0.0-rc.70/jsrender.min.js" integrity="sha256-3UBtL0tzgKVuJU8ZZiWLXEWGEjXEr6Z023rpauMnBUE=" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" src="/res/ts/bfs.js"></script>
|
||||
@RenderSection("scripts", false)
|
||||
<script id="result-template" type="text/x-jsrender">
|
||||
<a href="{{:Url}}" class="search-result-item" title="{{:Title}}">
|
||||
<h4 class="search-result-heading no-wrapping">{{:Label}}</h4>
|
||||
<p class="search-result-text">{{:Group}}</p>
|
||||
</a>
|
||||
</script>
|
||||
<script type="text/javascript" src="//s7.addthis.com/js/300/addthis_widget.js#pubid=ra-5431719a661cbfd0" async="async"></script>
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jsrender/1.0.0-rc.70/jsrender.min.js" integrity="sha256-3UBtL0tzgKVuJU8ZZiWLXEWGEjXEr6Z023rpauMnBUE=" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" src="/res/ts/bfs.js"></script>
|
||||
@RenderSection("scripts", false)
|
||||
<script id="result-template" type="text/x-jsrender">
|
||||
<a href="{{:Url}}" class="search-result-item" title="{{:Title}}">
|
||||
<h4 class="search-result-heading no-wrapping">{{:Label}}</h4>
|
||||
<p class="search-result-text">{{:Group}}</p>
|
||||
</a>
|
||||
</script>
|
||||
<script type="text/javascript" src="//s7.addthis.com/js/300/addthis_widget.js#pubid=ra-5431719a661cbfd0" async="async"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,6 +0,0 @@
|
|||
@{
|
||||
ViewBag.Title = $"{VariantTerms.Support_ThanksRegister} | {InvariantTerms.SiteName}";
|
||||
}
|
||||
|
||||
<h1>@VariantTerms.Support_ThanksRegister</h1>
|
||||
<p>@VariantTerms.Support_AccountValidation</p>
|
|
@ -47,13 +47,13 @@
|
|||
<package id="jquery.TypeScript.DefinitelyTyped" version="3.1.2" targetFramework="net462" />
|
||||
<package id="jQuery.Validation" version="1.16.0" targetFramework="net462" />
|
||||
<package id="jsrender.TypeScript.DefinitelyTyped" version="0.1.8" targetFramework="net461" />
|
||||
<package id="Microsoft.ApplicationInsights" version="2.2.0" targetFramework="net462" />
|
||||
<package id="Microsoft.ApplicationInsights" version="2.3.0" targetFramework="net462" />
|
||||
<package id="Microsoft.ApplicationInsights.Agent.Intercept" version="2.0.7" targetFramework="net462" />
|
||||
<package id="Microsoft.ApplicationInsights.DependencyCollector" version="2.2.0" targetFramework="net462" />
|
||||
<package id="Microsoft.ApplicationInsights.PerfCounterCollector" version="2.2.0" targetFramework="net462" />
|
||||
<package id="Microsoft.ApplicationInsights.Web" version="2.2.0" targetFramework="net462" />
|
||||
<package id="Microsoft.ApplicationInsights.WindowsServer" version="2.2.0" targetFramework="net462" />
|
||||
<package id="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel" version="2.2.0" targetFramework="net462" />
|
||||
<package id="Microsoft.ApplicationInsights.DependencyCollector" version="2.3.0" targetFramework="net462" />
|
||||
<package id="Microsoft.ApplicationInsights.PerfCounterCollector" version="2.3.0" targetFramework="net462" />
|
||||
<package id="Microsoft.ApplicationInsights.Web" version="2.3.0" targetFramework="net462" />
|
||||
<package id="Microsoft.ApplicationInsights.WindowsServer" version="2.3.0" targetFramework="net462" />
|
||||
<package id="Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel" version="2.3.0" targetFramework="net462" />
|
||||
<package id="Microsoft.AspNet.Mvc" version="5.2.3" targetFramework="net462" />
|
||||
<package id="Microsoft.AspNet.Razor" version="3.2.3" targetFramework="net462" />
|
||||
<package id="Microsoft.AspNet.WebApi" version="5.2.3" targetFramework="net462" />
|
||||
|
@ -69,8 +69,8 @@
|
|||
<package id="MongoDB.Bson" version="2.4.3" targetFramework="net462" />
|
||||
<package id="MongoDB.Driver" version="2.4.3" targetFramework="net462" />
|
||||
<package id="MongoDB.Driver.Core" version="2.4.3" targetFramework="net462" />
|
||||
<package id="Newtonsoft.Json" version="10.0.1" targetFramework="net462" />
|
||||
<package id="OneSignal.CSharp.SDK" version="0.9" targetFramework="net462" />
|
||||
<package id="Newtonsoft.Json" version="10.0.2" targetFramework="net462" />
|
||||
<package id="OneSignal.CSharp.SDK" version="0.10" targetFramework="net462" />
|
||||
<package id="RestSharp" version="105.2.3" targetFramework="net462" />
|
||||
<package id="System.Collections" version="4.3.0" targetFramework="net462" />
|
||||
<package id="System.Linq" version="4.3.0" targetFramework="net462" />
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -87,7 +87,8 @@ table
|
|||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.field-validation-error
|
||||
.field-validation-error,
|
||||
.text-danger
|
||||
{
|
||||
display: block;
|
||||
margin: #{(1em / 3)} 0;
|
||||
|
@ -145,7 +146,7 @@ nav#page-navigation
|
|||
|
||||
#page-navigation-links
|
||||
{
|
||||
margin: 0 -15px;
|
||||
margin: 0 -15px #{(1px / 3)}; /* bottom margin fixes Chrome in 4k */
|
||||
padding: 0;
|
||||
text-align: right;
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
"use strict";var BuildFeed;!function(e){function t(e){e.preventDefault();var t=this;t.nextElementSibling.classList.toggle("open")}function n(e){e.preventDefault();var t=this;t.parentElement.classList.toggle("open");var n=document.getElementById("menu-open-overlay");n.classList.add("open")}function a(e){e.preventDefault();for(var t=document.getElementsByClassName("dropdown-parent"),n=0;n<t.length;n++)t[n].classList.remove("open");var a=document.getElementById("menu-open-overlay");a.classList.remove("open")}function o(e){e.preventDefault();var t=this;document.cookie="bf_theme="+t.dataset.theme+"; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/",location.reload(!0)}function r(e){e.preventDefault();var t=this;document.cookie="bf_lang="+t.dataset.lang+"; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/",location.reload(!0)}function d(e){e.preventDefault();var t=document.getElementById("modal-search-overlay");t.classList.add("open")}function l(e){e.preventDefault();var t=document.getElementById("modal-search-overlay");t.classList.remove("open")}function c(e){e.preventDefault(),e.stopPropagation()}function i(e){var t=document.getElementById("modal-search-result");t.innerHTML="","undefined"!=typeof p&&clearTimeout(p),"undefined"!=typeof v&&v.readyState!==XMLHttpRequest.DONE&&v.abort(),p=setInterval(s,200)}function s(){"undefined"!=typeof p&&clearTimeout(p);var e=document.getElementById("modal-search-input");v=new XMLHttpRequest,v.onreadystatechange=u,v.open("GET","/api/GetSearchResult/"+e.value+"/",!0),v.setRequestHeader("accept","application/json"),v.send(null)}function u(e){if(v.readyState===XMLHttpRequest.DONE&&200===v.status){var t=document.getElementById("modal-search-result"),n=document.getElementById("result-template"),a=jsrender.templates(n.innerHTML),o=a.render(JSON.parse(v.responseText));t.innerHTML=o;for(var r=t.getElementsByTagName("a"),d=0;d<r.length;d++)r[d].addEventListener("click",function(e){e.preventDefault();var t=document.getElementById("modal-search-input");ga("send","pageview","/api/GetSearchResult/"+t.value+"/"),location.assign(e.currentTarget.href)})}}function m(e){for(var s=document.getElementsByClassName("dropdown-parent"),u=0;u<s.length;u++)for(var m=0;m<s[u].childNodes.length;m++){var v=s[u].childNodes[m];"A"===v.nodeName&&v.addEventListener("click",n)}var p=document.getElementById("menu-open-overlay");p.addEventListener("click",a);for(var g=document.getElementById("settings-theme-menu").getElementsByTagName("a"),u=0;u<g.length;u++)g[u].addEventListener("click",o);for(var f=document.getElementById("settings-lang-menu").getElementsByTagName("a"),u=0;u<f.length;u++)f[u].addEventListener("click",r);var h=document.getElementById("page-navigation-toggle");h.addEventListener("click",t);var E=document.getElementById("page-navigation-search");E.addEventListener("click",d);var y=document.getElementById("modal-search-overlay");y.addEventListener("click",l);var B=document.getElementById("modal-search");B.addEventListener("click",c);var L=document.getElementById("modal-search-input");L.addEventListener("keyup",i)}var v,p;e.MobileMenuToggle=t,e.DropdownClick=n,e.CloseDropdowns=a,e.SwitchTheme=o,e.SwitchLanguage=r,e.OpenSearch=d,e.CloseSearch=l,e.StopClick=c,e.InitiateSearch=i,e.SendSearch=s,e.CompleteSearch=u,e.BuildFeedSetup=m}(BuildFeed||(BuildFeed={})),window.addEventListener("load",BuildFeed.BuildFeedSetup);
|
||||
"use strict";var BuildFeed;!function(e){function t(e){e.preventDefault(),this.nextElementSibling.classList.toggle("open")}function n(e){e.preventDefault(),this.parentElement.classList.toggle("open"),document.getElementById("menu-open-overlay").classList.add("open")}function a(e){e.preventDefault();for(var t=document.getElementsByClassName("dropdown-parent"),n=0;n<t.length;n++)t[n].classList.remove("open");document.getElementById("menu-open-overlay").classList.remove("open")}function o(e){e.preventDefault();var t=this;document.cookie="bf_theme="+t.dataset.theme+"; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/",location.reload(!0)}function l(e){e.preventDefault();var t=this;document.cookie="bf_lang="+t.dataset.lang+"; expires=Fri, 31 Dec 9999 23:59:59 GMT; path=/",location.reload(!0)}function d(e){e.preventDefault(),document.getElementById("modal-search-overlay").classList.add("open")}function r(e){e.preventDefault(),document.getElementById("modal-search-overlay").classList.remove("open")}function c(e){e.preventDefault(),e.stopPropagation()}function i(e){document.getElementById("modal-search-result").innerHTML="",void 0!==p&&clearTimeout(p),void 0!==g&&g.readyState!==XMLHttpRequest.DONE&&g.abort(),p=setInterval(s,200)}function s(){void 0!==p&&clearTimeout(p);var e=document.getElementById("modal-search-input");g=new XMLHttpRequest,g.onreadystatechange=u,g.open("GET","/api/GetSearchResult/"+e.value+"/",!0),g.setRequestHeader("accept","application/json"),g.send(null)}function u(e){if(g.readyState===XMLHttpRequest.DONE&&200===g.status){var t=document.getElementById("modal-search-result"),n=document.getElementById("result-template"),a=jsrender.templates(n.innerHTML),o=a.render(JSON.parse(g.responseText));t.innerHTML=o;for(var l=t.getElementsByTagName("a"),d=0;d<l.length;d++)l[d].addEventListener("click",function(e){e.preventDefault();var t=document.getElementById("modal-search-input");ga("send","pageview","/api/GetSearchResult/"+t.value+"/"),location.assign(e.currentTarget.href)})}}function m(e){for(var s=document.getElementsByClassName("dropdown-parent"),u=0;u<s.length;u++)for(var m=0;m<s[u].childNodes.length;m++){var g=s[u].childNodes[m];"A"===g.nodeName&&g.addEventListener("click",n)}document.getElementById("menu-open-overlay").addEventListener("click",a);for(var p=document.getElementById("settings-theme-menu").getElementsByTagName("a"),u=0;u<p.length;u++)p[u].addEventListener("click",o);for(var v=document.getElementById("settings-lang-menu").getElementsByTagName("a"),u=0;u<v.length;u++)v[u].addEventListener("click",l);document.getElementById("page-navigation-toggle").addEventListener("click",t),document.getElementById("page-navigation-search").addEventListener("click",d),document.getElementById("modal-search-overlay").addEventListener("click",r),document.getElementById("modal-search").addEventListener("click",c),document.getElementById("modal-search-input").addEventListener("keyup",i)}var g,p;e.MobileMenuToggle=t,e.DropdownClick=n,e.CloseDropdowns=a,e.SwitchTheme=o,e.SwitchLanguage=l,e.OpenSearch=d,e.CloseSearch=r,e.StopClick=c,e.InitiateSearch=i,e.SendSearch=s,e.CompleteSearch=u,e.BuildFeedSetup=m}(BuildFeed||(BuildFeed={})),window.addEventListener("load",BuildFeed.BuildFeedSetup);
|
||||
//# sourceMappingURL=bfs.js.map
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user