Added the template SQL script to build the database, functions and stored procedures.

This commit is contained in:
2021-02-08 14:06:19 -08:00
parent debb4ebc61
commit 70bb9dd9e2
@@ -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: <Author,,Name>
-- Create date: <Create Date, ,>
-- Description: <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: <Author,,Name>
-- Create date: <Create Date, ,>
-- Description: <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: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <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: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <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: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <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: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <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: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <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: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <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: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <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