From 70bb9dd9e2083bb57930797e873ce003f28bd73c Mon Sep 17 00:00:00 2001 From: Garritt McCune Date: Mon, 8 Feb 2021 14:06:19 -0800 Subject: [PATCH] Added the template SQL script to build the database, functions and stored procedures. --- SecureCore/SQL Scripts/User Auth Template.sql | 568 ++++++++++++++++++ 1 file changed, 568 insertions(+) create mode 100644 SecureCore/SQL Scripts/User Auth Template.sql diff --git a/SecureCore/SQL Scripts/User Auth Template.sql b/SecureCore/SQL Scripts/User Auth Template.sql new file mode 100644 index 0000000..f31f731 --- /dev/null +++ b/SecureCore/SQL Scripts/User Auth Template.sql @@ -0,0 +1,568 @@ +USE [main] +-- Session Management Cheatsheet: +-- https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html + +IF OBJECT_ID('dbo.[ActivityLog]', 'U') IS NOT NULL + DROP TABLE [ActivityLog] + +IF OBJECT_ID('dbo.[Session]', 'U') IS NOT NULL + DROP TABLE [Session] + +IF OBJECT_ID('dbo.[Event]', 'U') IS NOT NULL + DROP TABLE [Event] + +IF OBJECT_ID('dbo.[EventLevel]', 'U') IS NOT NULL + DROP TABLE [EventLevel] + +IF OBJECT_ID('dbo.[Login]', 'U') IS NOT NULL + DROP TABLE [Login] + +IF OBJECT_ID('dbo.[User]', 'U') IS NOT NULL + DROP TABLE [User] + +CREATE TABLE [User] +( + [User ID] INT IDENTITY(1,1), + [Name] NVARCHAR(64) UNIQUE NOT NULL, + [Email] NVARCHAR(64) UNIQUE NOT NULL, + + PRIMARY KEY([User ID]) +) + +CREATE TABLE [Login] +( + [Login ID] INT NOT NULL IDENTITY(1, 1), + [Password Hash] VARCHAR(1024) NOT NULL, + [Salt] VARCHAR(512) NOT NULL, + [User Key] INT NOT NULL, + [Login Attempts] INT NOT NULL DEFAULT 0, + [Last Login Attempt] DATETIME, + + PRIMARY KEY([Login ID]), + FOREIGN KEY ([User Key]) REFERENCES [User]([User ID]) +) + +CREATE TABLE [Session] +( + [Session ID] INT NOT NULL IDENTITY(1, 1), + [Session Token] VARCHAR(1024) UNIQUE NOT NULL, + [Expiration Date] DATETIME NOT NULL, + [User ID] INT NOT NULL, + [IP Address] VARCHAR(256) NOT NULL, + [User Agent] VARCHAR(256) NOT NULL, + [Pending Reset] BIT NOT NULL DEFAULT 0, + + PRIMARY KEY([Session ID]), + FOREIGN KEY([User ID]) REFERENCES [User]([User ID]) +) + +CREATE TABLE [EventLevel] +( + [Event Level ID] TINYINT NOT NULL, + [Event Display Name] VARCHAR(64) NOT NULL, + + PRIMARY KEY([Event Level ID]) +) + +CREATE TABLE [Event] +( + [Event ID] INT NOT NULL IDENTITY(1, 1), + [Event Name] VARCHAR(128) NOT NULL, + [Event Level ID] TINYINT NOT NULL, + + PRIMARY KEY([Event ID]), + FOREIGN KEY([Event Level ID]) REFERENCES [EventLevel]([Event Level ID]) +) + +CREATE TABLE [ActivityLog] +( + [Log ID] INT NOT NULL IDENTITY(1, 1), + [Event ID] INT NOT NULL, + [Event Description] NVARCHAR(512) NOT NULL, + [Timestamp] DATETIME NOT NULL, + [Component] VARCHAR(128) NOT NULL, + + PRIMARY KEY([Log ID]), + FOREIGN KEY([Event ID]) REFERENCES [Event] ([Event ID]) +) + +-- The smaller the number the higher the severity. +INSERT INTO [EventLevel] ([Event Level ID], [Event Display Name]) VALUES(0, 'Critical') +INSERT INTO [EventLevel] ([Event Level ID], [Event Display Name]) VALUES(1, 'Error') +INSERT INTO [EventLevel] ([Event Level ID], [Event Display Name]) VALUES(2, 'Warning') +INSERT INTO [EventLevel] ([Event Level ID], [Event Display Name]) VALUES(3, 'Information') +INSERT INTO [EventLevel] ([Event Level ID], [Event Display Name]) VALUES(255, 'Debug') + +INSERT INTO [Event] ([Event Name], [Event Level ID]) VALUES ('Login', 3) +INSERT INTO [Event] ([Event Name], [Event Level ID]) VALUES ('Logout', 3) +INSERT INTO [Event] ([Event Name], [Event Level ID]) VALUES ('Registration', 3) +INSERT INTO [Event] ([Event Name], [Event Level ID]) VALUES ('Session Expiration', 3) +INSERT INTO [Event] ([Event Name], [Event Level ID]) VALUES ('Password Reset Request', 3) +INSERT INTO [Event] ([Event Name], [Event Level ID]) VALUES ('Password Reset', 3) + +/* +INSERT INTO [User] (Name, Email) VALUES ('glm94', 'glmccune@gmail.com') +INSERT INTO [User] (Name, Email) VALUES ('admin', 'admin@gmail.com') +INSERT INTO [User] (Name, Email) VALUES ('nobody', 'none@gmail.com') +*/ +-- Define Functions +GO +/****** Object: UserDefinedFunction [dbo].[UserExists] Script Date: 1/13/2021 7:23:30 PM ******/ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +-- ============================================= +-- Author: +-- Create date: +-- Description: +-- ============================================= +CREATE OR ALTER FUNCTION [dbo].[UserExists] +( + @UserName NVARCHAR(512) +) +RETURNS BIT +AS +BEGIN + + DECLARE @Exists BIT = 0 + + IF EXISTS ( SELECT 1 FROM [User] WHERE [Name] = @UserName ) + BEGIN + SET @Exists = 1; + END + + RETURN @Exists; +END +GO +GO +/****** Object: UserDefinedFunction [dbo].[UserExists] Script Date: 1/14/2021 5:31:27 PM ******/ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +-- ============================================= +-- Author: +-- Create date: +-- Description: +-- ============================================= +CREATE OR ALTER FUNCTION [dbo].[EmailInUse] +( + @Email NVARCHAR(512) +) +RETURNS BIT +AS +BEGIN + + DECLARE @Exists BIT = 0 + + IF EXISTS ( SELECT 1 FROM [User] WHERE [Email] = @Email ) + BEGIN + SET @Exists = 1; + END + + RETURN @Exists; +END +GO +GO +-- Define Stored Procedures +/****** Object: UserDefinedFunction [dbo].[GetUserPasswordHashAndSalt] Script Date: 1/13/2021 7:23:30 PM ******/ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +-- ============================================= +-- Author: +-- Create date: +-- Description: +-- ============================================= +CREATE OR ALTER FUNCTION [dbo].[GetUserPasswordHashAndSalt] +( + @Username NVARCHAR(64) +) +RETURNS TABLE +AS +RETURN +( + SELECT [Login].[Password Hash], [Login].Salt + FROM [Login] + INNER JOIN [User] ON + [User].[User ID] = [Login].[User Key] + WHERE [User].[Name] = @Username +) +GO +/****** Object: StoredProcedure [dbo].[LoginUser] Script Date: 1/13/2021 7:23:30 PM ******/ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +-- ============================================= +-- Author: +-- Create date: +-- Description: +-- ============================================= +CREATE OR ALTER PROCEDURE [dbo].[LoginUser] + @UserName NVARCHAR(64), + @SessionToken VARCHAR(1024), + @ExpirationDate DATETIME, + @IpAddress VARCHAR(256), + @UserAgent VARCHAR(256) +AS +BEGIN + -- SET NOCOUNT ON added to prevent extra result sets from + -- interfering with SELECT statements. + SET NOCOUNT, XACT_ABORT ON; + + DECLARE @UserID INT + DECLARE @EventID INT + DECLARE @Description NVARCHAR(512) + + SELECT @UserID = [User ID] FROM [User] WHERE [User].[Name] = @UserName + + BEGIN TRY + BEGIN TRANSACTION; + IF dbo.UserExists(@UserName) = 0 + BEGIN; + THROW 51000, 'User does not exist', 1; + END; + + INSERT INTO [Session] ([Session Token], [Expiration Date], [User ID], [IP Address], [User Agent], [Pending Reset]) VALUES (@SessionToken, @ExpirationDate, @UserID, @IpAddress, @UserAgent, 0) + + SELECT @EventID = [Event ID] FROM [Event] WHERE [Event Name] = 'Login' + + SET @Description = FORMATMESSAGE('"%s" logged in', @UserName) + + INSERT INTO [ActivityLog] ([Event ID], [Event Description], [Timestamp], [Component]) VALUES (@EventID, @Description, SYSDATETIME(), 'SP_LoginUser') + + COMMIT TRANSACTION; + END TRY + BEGIN CATCH + + IF @@TRANCOUNT > 0 + BEGIN + ROLLBACK TRANSACTION + END; + + THROW; + + END CATCH; + + SET NOCOUNT, XACT_ABORT OFF; +END +GO +/****** Object: StoredProcedure [dbo].[LogoutUser] Script Date: 1/13/2021 7:23:30 PM ******/ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +-- ============================================= +-- Author: +-- Create date: +-- Description: +-- ============================================= +CREATE OR ALTER PROCEDURE [dbo].[LogoutUser] + @SessionToken NVARCHAR(1024) +AS +BEGIN + -- SET NOCOUNT ON added to prevent extra result sets from + -- interfering with SELECT statements. + SET NOCOUNT ON; + + DECLARE @SessionID INT + DECLARE @UserName NVARCHAR(512) + DECLARE @Success BIT = 0 + DECLARE @EventID INT + + SELECT @SessionID = [Session ID], @UserName = [Name] FROM [Session] INNER JOIN [User] ON [Session].[User ID] = [User].[User ID] WHERE [Session Token] = @SessionToken + SELECT @EventID = [Event ID] FROM [Event] WHERE [Event Name] = 'Logout' + + SET NOCOUNT, XACT_ABORT ON; + BEGIN TRY + BEGIN TRANSACTION; + + IF @SessionID IS NULL + BEGIN; + THROW 51000, 'Session token not found', 1; + END; + + DELETE FROM [Session] WHERE [Session ID] = @SessionID + + IF @EventID IS NOT NULL + INSERT INTO ActivityLog ([Event Description], [Timestamp], [Component], [Event ID]) VALUES (FORMATMESSAGE('User "%s" has logged out', @UserName), SYSDATETIME(), 'SP_LogoutUser', @EventID) + + COMMIT TRANSACTION; + + SET @Success = 1 + + END TRY + BEGIN CATCH + IF @@TRANCOUNT > 0 + BEGIN + ROLLBACK TRANSACTION + END; + + THROW; + + END CATCH; + SET NOCOUNT, XACT_ABORT OFF; + + SELECT @Success AS Success +END +GO +/****** Object: StoredProcedure [dbo].[LogPasswordResetRequest] Script Date: 1/13/2021 7:23:30 PM ******/ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +-- ============================================= +-- Author: +-- Create date: +-- Description: +-- ============================================= +CREATE OR ALTER PROCEDURE [dbo].[LogPasswordResetRequest] + @Email NVARCHAR(64), + @SessionToken VARCHAR(1024), + @ExpirationDate DATETIME, + @IpAddress VARCHAR(256), + @UserAgent VARCHAR(256) +AS +BEGIN + -- SET NOCOUNT ON added to prevent extra result sets from + -- interfering with SELECT statements. + SET NOCOUNT, XACT_ABORT ON; + + DECLARE @UserID INT + DECLARE @EventID INT + DECLARE @Description NVARCHAR(512) + DECLARE @UserName NVARCHAR(64) + + SELECT @UserID = [User ID], @UserName = [Name] FROM [User] WHERE [User].[Email] = @Email + + BEGIN TRY + BEGIN TRANSACTION; + IF dbo.UserExists(@UserName) = 0 + BEGIN; + THROW 51000, 'User does not exist', 1; + END; + + INSERT INTO [Session] ([Session Token], [Expiration Date], [User ID], [IP Address], [User Agent], [Pending Reset]) VALUES (@SessionToken, @ExpirationDate, @UserID, @IpAddress, @UserAgent, 1) + + SELECT @EventID = [Event ID] FROM [Event] WHERE [Event Name] = 'Password Reset Request' + + SET @Description = FORMATMESSAGE('"%s" requested a password reset', @UserName) + + INSERT INTO [ActivityLog] ([Event ID], [Event Description], [Timestamp], [Component]) VALUES (@EventID, @Description, SYSDATETIME(), 'SP_LogPasswordResetRequest') + + COMMIT TRANSACTION; + END TRY + BEGIN CATCH + + IF @@TRANCOUNT > 0 + BEGIN + ROLLBACK TRANSACTION + END; + + THROW; + + END CATCH; + + SET NOCOUNT, XACT_ABORT OFF; +END +GO +/****** Object: StoredProcedure [dbo].[RegisterNewUser] Script Date: 1/13/2021 7:23:30 PM ******/ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +-- ============================================= +-- Author: +-- Create date: +-- Description: +-- ============================================= +CREATE OR ALTER PROCEDURE [dbo].[RegisterNewUser] + @UserName NVARCHAR(512), + @Email NVARCHAR(512), + @Password NVARCHAR(1024), + @Salt NVARCHAR(512), + @SessionToken NVARCHAR(1024), + @ExpirationDate DATETIME, + @UserAgent VARCHAR(256), + @IpAddress VARCHAR(256) +AS +BEGIN + -- SET NOCOUNT ON added to prevent extra result sets from + -- interfering with SELECT statements. + DECLARE @UserID INT + DECLARE @EventID INT + DECLARE @SessionID INT + + SET NOCOUNT, XACT_ABORT ON; + + BEGIN TRY + + BEGIN TRANSACTION; + IF dbo.UserExists(@UserName) = 1 + BEGIN; + THROW 51000, 'User already exists', 1; + END; + + SELECT @EventID = [Event ID] FROM [Event] WHERE [Event Name] = 'Registration' + + IF @EventID = NULL OR @EventID = 0 + BEGIN; + THROW 51001, 'Registration event type not found', 1; + END; + + INSERT INTO [User] (Name, Email) VALUES (@UserName, @Email) + + SELECT @UserID = SCOPE_IDENTITY() + + INSERT INTO [Login] ([Password Hash], [Salt], [User Key]) VALUES (@Password, @Salt, @UserId) + + INSERT INTO [Session] ([Session Token], [Expiration Date], [User ID], [User Agent], [IP Address]) VALUES (@SessionToken, @ExpirationDate, @UserId, @UserAgent, @IpAddress) + + SELECT @SessionID = SCOPE_IDENTITY() + + INSERT INTO [ActivityLog] ([Event ID], [Event Description], [Timestamp], [Component]) VALUES (@EventID, FORMATMESSAGE('Registered user "%s".', @UserName), SYSDATETIME(), 'SP_RegisterUser') + + SELECT @UserID AS UserKey + + COMMIT TRANSACTION; + + END TRY + BEGIN CATCH + + IF @@TRANCOUNT > 0 + BEGIN + ROLLBACK TRANSACTION + END; + + THROW; + + END CATCH; + + SET NOCOUNT, XACT_ABORT OFF; +END +GO +/****** Object: StoredProcedure [dbo].[ResetPassword] Script Date: 1/13/2021 7:23:30 PM ******/ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +-- ============================================= +-- Author: +-- Create date: +-- Description: +-- ============================================= +CREATE OR ALTER PROCEDURE [dbo].[ResetPassword] + @PasswordHash VARCHAR(1024), + @Salt VARCHAR(512), + @SessionToken VARCHAR(1024) +AS +BEGIN + -- SET NOCOUNT ON added to prevent extra result sets from + -- interfering with SELECT statements. + DECLARE @Username NVARCHAR(64) + DECLARE @UserID INT + DECLARE @EventID INT + + SELECT @UserID = [User ID] FROM [Session] WHERE [Session Token] = @SessionToken + SELECT @Username = [Name] FROM [User] WHERE [User ID] = @UserID + + SET NOCOUNT, XACT_ABORT ON; + + BEGIN TRY + + BEGIN TRANSACTION; + IF dbo.UserExists(@UserName) = 0 + BEGIN; + THROW 51002, 'User does not exist', 1; + END; + + SELECT @EventID = [Event ID] FROM [Event] WHERE [Event Name] = 'Password Reset' + + IF @EventID = NULL OR @EventID = 0 + BEGIN; + THROW 51001, 'Password reset event type not found', 1; + END; + + UPDATE [Login] SET [Password Hash] = @PasswordHash, [Salt] = @Salt, [Last Login Attempt] = NULL WHERE [User Key] = @UserID + + DELETE FROM [Session] WHERE [User ID] = @UserID + + INSERT INTO [ActivityLog] ([Event ID], [Event Description], [Timestamp], [Component]) VALUES (@EventID, FORMATMESSAGE('User "%s" has had their password reset.', @UserName), SYSDATETIME(), 'SP_ResetPassword') + + COMMIT TRANSACTION; + + END TRY + BEGIN CATCH + + IF @@TRANCOUNT > 0 + BEGIN + ROLLBACK TRANSACTION + END; + + THROW; + + END CATCH; + + SET NOCOUNT, XACT_ABORT OFF; +END +GO +/****** Object: StoredProcedure [dbo].[ValidateSessionToken] Script Date: 1/13/2021 7:23:30 PM ******/ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +-- ============================================= +-- Author: +-- Create date: +-- Description: +-- ============================================= +CREATE OR ALTER PROCEDURE [dbo].[ValidateSessionToken] + @SessionToken VARCHAR(1024), + @IsResetToken BIT +AS +BEGIN + -- SET NOCOUNT ON added to prevent extra result sets from + -- interfering with SELECT statements. + SET NOCOUNT, XACT_ABORT OFF; + DECLARE @SessionExpiration DATETIME + DECLARE @SessionID INT + DECLARE @SessionIsValid BIT = 0 + DECLARE @UserID INT + DECLARE @EventID TINYINT + + SELECT @SessionID = [Session ID], @SessionExpiration = [Expiration Date], @UserID = [User ID] FROM [Session] WHERE [Session Token] = @SessionToken AND [Pending Reset] = @IsResetToken + SELECT @EventID = [Event ID] FROM [Event] WHERE [Event Name] = 'Session Expiration' + + SET NOCOUNT, XACT_ABORT ON; + BEGIN TRY + BEGIN TRANSACTION; + -- Code here + IF @SessionID IS NOT NULL + BEGIN + -- Sesssion found. + IF @SessionExpiration > SYSDATETIME() + SET @SessionIsValid = 1 + ELSE + BEGIN + DELETE FROM [Session] WHERE [Session ID] = @SessionID + + IF @EventID IS NOT NULL + INSERT INTO [ActivityLog] ([Event Description], [Timestamp], [Event ID], [Component]) VALUES ('Session token has expired', SYSDATETIME(), @EventID, 'SP_ValidateSessionToken') + END + END + COMMIT TRANSACTION; + END TRY + BEGIN CATCH + IF @@TRANCOUNT > 0 + BEGIN + ROLLBACK TRANSACTION + END; + END CATCH; + SET NOCOUNT, XACT_ABORT OFF; + + SELECT @SessionIsValid AS SessionIsValid +END +GO