public async Task <UserRefToken> GetUserRefToken(IWebRequest req) { var authentication = req.GetAuthenticationHeaderValue(); UserRefToken userRefToken = await this.AuthenticateAsync(authentication?.Scheme, authentication?.Parameter) as UserRefToken; if (userRefToken == null) { throw new UnauthorizedException(); } return(userRefToken); }
/// <summary> /// Validates the auth token id returning an <see cref="AuthToken"/> instance /// </summary> /// <param name="authType"></param> /// <param name="authValue"></param> /// <returns>An <see cref="AuthToken"/> instance</returns> public async Task <AuthToken> AuthenticateAsync(string authType, string authValue) { var authTokenFieldList = new List <string>(); if (!string.IsNullOrEmpty(this.authTokenTableIdFieldName)) { authTokenFieldList.Add($"{ this.authTokenTableIdFieldName}"); } if (!string.IsNullOrEmpty(this.authTokenTableUserIdFieldName)) { authTokenFieldList.Add($"{ this.authTokenTableUserIdFieldName}"); } if (!string.IsNullOrEmpty(this.authTokenTableExpiresAtFieldName)) { authTokenFieldList.Add($"{ this.authTokenTableExpiresAtFieldName}"); } var userFieldList = new List <string>(); if (!string.IsNullOrEmpty(this.userTableAccountIdFieldName)) { userFieldList.Add($"{ this.userTableAccountIdFieldName}"); } if (!string.IsNullOrEmpty(this.userTableUsernameFieldName)) { userFieldList.Add($"{ this.userTableUsernameFieldName}"); } if (!string.IsNullOrEmpty(this.userTableRoleFieldName)) { userFieldList.Add($"{ this.userTableRoleFieldName}"); } Dict result; if (this.database.CanJoin) { var fieldList = new List <List <string> > { authTokenFieldList.Select(x => $"at.{x}").ToList(), userFieldList.Select(x => $"u.{x}").ToList() }.SelectMany(x => x); var sql = $"SELECT {string.Join(",", fieldList)} FROM {this.authTokenTableName} at INNER JOIN {this.userTableName} u ON at.{this.authTokenTableUserIdFieldName}=u.{this.userTableIdFieldName} WHERE at.{authTokenTableIdFieldName}=@authTokenId"; result = await this.database.SelectRowAsync(sql, new { authTokenId = authValue }); } else { if (!string.IsNullOrEmpty(this.authTokenTableUserIdFieldName)) { authTokenFieldList.Add($"{ this.authTokenTableUserIdFieldName}"); } var authTokenSql = $"SELECT {string.Join(",", authTokenFieldList)} FROM {this.authTokenTableName} WHERE {authTokenTableIdFieldName}=@authTokenId"; result = await this.database.SelectRowAsync(authTokenSql, new { authTokenId = authValue }); if (result != null) { var userId = result.GetAs(this.authTokenTableUserIdFieldName, ""); var userSql = $"SELECT {string.Join(",", userFieldList)} FROM {this.userTableName} WHERE {this.userTableIdFieldName}=@userId"; Dict userResult = await this.database.SelectRowAsync(userSql, new { userId }); result.UpdateFrom(userResult); } } logger.Debug($"Authenticate():authTokenDict={result}"); if (result == null) { throw new UnauthorizedException(); } var authToken = UserRefToken.FromDict(result, this.authTokenTableIdFieldName, this.authTokenTableUserIdFieldName, this.userTableUsernameFieldName, this.userTableRoleFieldName, this.userTableAccountIdFieldName, this.authTokenTableExpiresAtFieldName); logger.Debug($"Authenticate():authToken.expiresAt={authToken.expiresAt}"); if (authToken.expiresAt == DateTime.MinValue || authToken.expiresAt < DateTime.Now) { throw new UnauthorizedException(); } return(authToken); }
/// <summary> /// Registers a new user /// </summary> /// <param name="input"></param> /// <param name="notifyData"></param> /// <returns></returns> public async Task <UserRefToken> RegisterAsync(dynamic input, Dict notifyData = null) { Dict registration = this.ConvertInputToDict(input); // Lookup existing user, account, and username string existingUserId = registration.GetAs("user_id", (string)null); string existingAccountId = null; string existingUsername = null; logger.Trace($"RegisterAsync():existingUserId={existingUserId}"); if (!string.IsNullOrEmpty(existingUserId)) { Dict row = await this.database.SelectRowAsync($"SELECT {this.userTableAccountIdFieldName}, {this.userTableUsernameFieldName} FROM {this.userTableName}", existingUserId); existingAccountId = row.GetAs(this.userTableAccountIdFieldName, ""); existingUsername = row.GetAs(this.userTableUsernameFieldName, ""); } // Validate username string username = this.usernameFieldValidator.Validate(registration?.GetAs(this.userTableUsernameFieldName, (string)null)); logger.Trace($"RegisterAsync():username={username}"); if (username != existingUsername) { Dict existingUserByUsername = await this.database.SelectRowAsync(this.userTableName, new Dict { { this.userTableUsernameFieldName, username } }); if (existingUserByUsername != null) { throw new Exception("Username '" + username + "' is unavailable"); } } // Validate password string password = this.passwordFieldValidator.Validate(registration?.GetAs("password", (string)null)); // Validate email and phone string email = this.emailFieldValidator.Validate(registration?.GetAs(this.userTableEmailFieldName, (string)null)); string phone = this.phoneFieldValidator.Validate(registration?.GetAs(this.userTablePhoneFieldName, (string)null)); // Validate first and last name string firstName = this.nameFieldValidator.Validate(registration?.GetAs(this.userTableFirstNameFieldName, (string)null)); string lastName = this.nameFieldValidator.Validate(registration?.GetAs(this.userTableLastNameFieldName, (string)null)); string role = string.IsNullOrEmpty(this.userTableRoleFieldName) ? this.defaultRole : registration?.GetAs(this.userTableRoleFieldName, this.defaultRole); // Create salt and password hash string salt = Guid.NewGuid().ToString(); string passwordHash = $"{salt} {password}".Hash(); // Create account string accountId; using (ITransaction transaction = await this.database.BeginTransactionAsync()) { if (string.IsNullOrEmpty(existingAccountId)) { var extraAccountInfo = this.getExtraAccountInfo == null ? null : this.getExtraAccountInfo(registration); accountId = await transaction.InsertAsync <string>(this.accountTableName, extraAccountInfo); } else { accountId = existingAccountId; } // Create user record Dict user = new Dict { { this.userTableAccountIdFieldName, accountId }, { this.userTableUsernameFieldName, username }, { this.userTableSaltFieldName, salt }, { this.userTablePasswordHashFieldName, passwordHash }, { this.userTableEmailFieldName, email }, { this.userTablePhoneFieldName, phone }, { this.userTableFirstNameFieldName, firstName }, { this.userTableLastNameFieldName, lastName }, }; if (!string.IsNullOrEmpty(this.userTableRoleFieldName) && !string.IsNullOrEmpty(this.defaultRole)) { user[this.userTableRoleFieldName] = role; } Dict extraUserInfo = this.getExtraUserInfo == null ? null : this.getExtraUserInfo(registration); if (extraUserInfo != null) { user.UpdateFrom(extraUserInfo); } string userId; if (string.IsNullOrEmpty(existingUserId)) { userId = await transaction.InsertAsync <string>(this.userTableName, user); } else { userId = existingUserId; user[this.userTableIdFieldName] = userId; await transaction.UpdateAsync(this.userTableName, user); } if (this.onRegister != null) { transaction.OnCommit(async() => { var registerUser = new Dict { { this.userTableIdFieldName, userId }, { this.userTableAccountIdFieldName, accountId }, { this.userTableUsernameFieldName, username }, { this.userTableEmailFieldName, email }, { this.userTablePhoneFieldName, phone }, { this.userTableFirstNameFieldName, firstName }, { this.userTableLastNameFieldName, lastName }, }; if (notifyData != null) { registerUser.UpdateFrom(notifyData); } try { await this.onRegister(string.IsNullOrEmpty(existingAccountId), registerUser); } catch (Exception e) { logger.Debug(e); } }); } UserRefToken userRefToken = await this.CreateUserRefTokenAsync(transaction, userId, username, role, accountId); await transaction.CommitAsync(); return(userRefToken); } }
/// <summary> /// Call to setup a Web API with the specified <paramref name="webApi"/> /// </summary> /// <remarks> /// The following API URLs will be setup... /// <code> /// GET /api/auth/check-username/{username} /// GET /api/auth/check-auth-token/{id} /// POST /api/auth/create-anonymous /// POST /api/auth/register /// POST /api/auth/login /// POST /api/auth/forgot-password /// POST /api/auth/reset-password /// POST /api/auth/verify /// </code> /// </remarks> /// <param name="webApi"></param> /// <param name="pathPrefix">Defaults to /api/auth</param> public void SetupWebApi(IWebApi webApi, string pathPrefix = "/api/auth") { webApi.OnGet($"{pathPrefix}/check-username/{{username}}", async(req, res) => { string username = req.PathParams.GetAs("username", (string)null); logger.Debug($"/check-username/{username}"); Dict user = await this.LookupUsernameAsync(username, this.userTableIdFieldName); bool available = user == null; await res.WriteAsJsonAsync(available); }); webApi.OnGet($"{pathPrefix}/check-user-ref-token/{{id}}", async(req, res) => { string id = req.PathParams.GetAs("id", (string)null); string rawVersionText = req.QueryParams.GetAs("v", ""); string versionText = VERSION_CLEAN_REGEX.Replace(rawVersionText, ""); Version version = string.IsNullOrEmpty(versionText) ? null : Version.Parse(versionText); logger.Debug($"/check-auth-token/{id}?v={version}"); //?join_code={joinCode}"); if (this.onCheckVersion != null) { this.onCheckVersion(version); } AuthToken authToken = await this.AuthenticateAsync(UserRefTokenAuthenticator.AUTH_TYPE, id); await res.WriteAsJsonAsync(authToken); }); webApi.OnPost($"{pathPrefix}/create-anonymous", async(req, res) => { Dict data = await req.ParseAsJsonAsync <Dict>(); AuthToken authToken = await this.CreateAnonymousUserAsync(); await res.WriteAsJsonAsync(authToken); }); webApi.OnPost($"{pathPrefix}/register", async(req, res) => { Dict registration = await req.ParseAsJsonAsync <Dict>(); AuthToken authToken = await this.RegisterAsync(registration); await res.WriteAsJsonAsync(authToken); }); webApi.OnPost($"{pathPrefix}/login", async(req, res) => { Dict login = await req.ParseAsJsonAsync <Dict>(); AuthToken authToken = await this.LoginAsync(login); await res.WriteAsJsonAsync(authToken); }); webApi.OnPost($"{pathPrefix}/forgot-password", async(req, res) => { Dict data = await req.ParseAsJsonAsync <Dict>(); string username = data.GetAs("username", (string)null); await this.ForgotPasswordAsync(username); }); webApi.OnPost($"{pathPrefix}/reset-password", async(req, res) => { Dict resetPassword = await req.ParseAsJsonAsync <Dict>(); AuthToken authToken = await this.ResetPasswordAsync(resetPassword); await res.WriteAsJsonAsync(authToken); }); webApi.OnPost($"{pathPrefix}/forgot-username", async(req, res) => { Dict data = await req.ParseAsJsonAsync <Dict>(); string contact = data.GetAs("contact", (string)null); await this.ForgotUsernameAsync(contact); }); webApi.OnPost($"{pathPrefix}/send-email-verify-code", async(req, res) => { UserRefToken userRefToken = await this.GetUserRefToken(req); string email = await this.database.SelectValueAsync <string>("SELECT email FROM user", vars: userRefToken.userId); await this.SendVerifyCodeAsync(email); }); webApi.OnPost($"{pathPrefix}/send-phone-verify-code", async(req, res) => { UserRefToken userRefToken = await this.GetUserRefToken(req); string phone = await this.database.SelectValueAsync <string>("SELECT phone FROM user", vars: userRefToken.userId); await this.SendVerifyCodeAsync(phone); }); webApi.OnPost($"{pathPrefix}/verify", async(req, res) => { Dict data = await req.ParseAsJsonAsync <Dict>(); await this.VerifyAsync(data); }); }