use AnP

if object_id(N'dbo.SessionsPermissionsView', N'V') is not null drop view dbo.SessionsPermissionsView
go
create view dbo.SessionsPermissionsView as 
    select distinct 
        sessions.id as [session], 
        permissions.name as [permission]
    from dbo.SessionsActived sessions 
    join dbo.Users users on sessions.[user] = users.id 
    join dbo.Ips ips on sessions.[ip] = ips.id 
    join dbo.GroupsUsers groups_users on groups_users.belongs = 1 and users.id = groups_users.[user] 
    join dbo.GroupsIps groups_ips on groups_ips.belongs = 1 and ips.id = groups_ips.[ip] 
    join dbo.Groups groups on groups_users.[group] = groups.id or groups_ips.[group] = groups.id 
    join dbo.PermissionsGroups permissions_groups on permissions_groups.belongs = 1 and groups.id = permissions_groups.[group] 
    join dbo.Permissions permissions on permissions_groups.[permission] = permissions.id
    where 
        users.date_out is null and 
        ips.date_out is null and 
        groups.date_out is null and 
        groups_users.date_out is null and 
        groups_ips.date_out is null and 
        permissions.date_out is null and 
        permissions_groups.date_out is null 
go

if object_id(N'dbo.sessions_validate', N'FN') is not null drop function dbo.sessions_validate
go
create function dbo.sessions_validate(
    @session integer, 
    @permissions varchar(max) = ''
) returns integer begin 

    declare @error integer = (case 
        when @session is null then dbo.bw(1, 1) 
        when @session < 1 then dbo.bw(1, 2) 
    else isnull((
        select 0 from dbo.SessionsActived where id = @session 
    ), isnull((
        select dbo.bw(1, 4) from dbo.Sessions where id = @session 
    ), dbo.bw(1, 3))) end)

    if @error = 0 and @permissions != '' and (
        select top 1 0 from dbo.SessionsPermissionsView where [session] = @session and [permission] in (
            select [value] from string_split(@permissions, ',')
        )
    ) is null
        set @error = dbo.bw(1, 5)

    return @error
end
go

if object_id(N'dbo.sessions_nick_validate', N'FN') is not null drop function dbo.sessions_nick_validate
go
create function dbo.sessions_nick_validate(
    @nick varchar(32)
) returns integer begin 
    return (case 
        when @nick is null then dbo.bw(1, 1) 
        when @nick = '' then dbo.bw(1, 2) 
        when len(@nick) > 32 then dbo.bw(1, 3) 
        when @nick like '%[^a-zA-Z0-9]%' then dbo.bw(1, 4) 
        when @nick like '[^a-zA-Z]%' then dbo.bw(1, 5) 
    else 0 end)
end
go

if object_id(N'dbo.sessions_password_validate', N'FN') is not null drop function dbo.sessions_password_validate
go
create function dbo.sessions_password_validate(
    @password varchar(256)
) returns integer begin 
    return (case 
        when @password is null then dbo.bw(1, 1) 
        when @password = '' then dbo.bw(1, 2) 
        when len(@password) > 256 then dbo.bw(1, 3) 
    else (
        (case when @password collate Latin1_General_100_CS_AS_SC_UTF8 not like '%[a-z]%' then dbo.bw(1, 4) else 0 end) | 
        (case when @password collate Latin1_General_100_CS_AS_SC_UTF8 not like '%[A-Z]%' then dbo.bw(1, 5) else 0 end) | 
        (case when @password not like '%[0-9]%' then dbo.bw(1, 6) else 0 end) | 
        (case when @password not like '%[^a-zA-Z0-9]%' then dbo.bw(1, 7) else 0 end) | 
    0) end)
end
go

if object_id(N'dbo.ips_set', N'P') is not null drop procedure dbo.ips_set
go
create procedure dbo.ips_set(
    @procedure integer, 
    @session integer, 
    @address varchar(39), 
    @error integer output, 
    @id integer output
) as begin set nocount on 

    set @error = (case 
        when @address is null then dbo.bw(1, 1) 
        when @address = '' then dbo.bw(1, 2) 
        when len(@address) > 39 then dbo.bw(1, 3) 
        when @address not like '[0-9a-fA-F:.]%' then dbo.bw(1, 4) 
        when not ((
            @address like '%._%._%._%' and 
            @address not like '%[^0-9.]%' and 
            len(@address) - len(replace(@address, '.', '')) = 3 and 
            parsename(@address, 1) between 0 and 255 and 
            parsename(@address, 2) between 0 and 255 and 
            parsename(@address, 3) between 0 and 255 and 
            parsename(@address, 4) between 0 and 255 
        ) or (
            @address not like '%[^0-9a-fA-F:]%' and 
            @address like '%:%' and 
            @address not like '%:::%' and 
            @address not like '%[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]%' and 
            len(@address) - len(replace(@address, ':', '')) between 2 and 7 
        )) then dbo.bw(1, 5) 
    else 0 end)

    if @error = 0 begin 
        insert into dbo.Ips([procedure], [session], [address]) values (@procedure, @session, @address)
        set @id = scope_identity()
    end

end
go

if object_id(N'dbo.sessions_register', N'P') is not null drop procedure dbo.sessions_register
go
create procedure dbo.sessions_register(
    @session integer, 
    @nick varchar(32), 
    @password varchar(256), 
    @email varchar(256),
    @errors varchar(512) output
) as begin set nocount on 

    declare @database varchar(64) = db_name()
    declare @procedure_id integer
    declare @parameters varchar(max) = '{' + 
        '"session":' + convert(varchar(14), @session) + ',' + 
        '"nick":"' + @nick + '",' + 
        -- '"password":"' + @password + '",' + 
        '"email":"' + @email + '",' + 
        '"id":null' + 
    '}'
    declare @error_code integer = 0
    declare @password_hash varbinary(64)
    declare @id integer = null

    execute dbo.procedure_get_id @database, 'sessions_register', @procedure_id output
    set @errors = ''

    begin try
    
        declare @nick_error integer = dbo.sessions_nick_validate(@nick)
        declare @password_error integer = dbo.sessions_password_validate(@password)

        set @error_code = (
            dbo.bw(dbo.sessions_validate(@session, 'guest'), 1) | 
            dbo.bw(@nick_error, 7) | 
            dbo.bw(@password_error, 14) | 
        0)

        if @error_code = 0 begin 
            if (select top 1 0 from dbo.Users where date_out is null and nick = @nick) is not null
                set @error_code = @error_code | dbo.bw(1, 13)
            if @error_code = 0 begin 
                insert into dbo.Users(nick, password) values 
                    (@nick, hashbytes(dbo.settings_get('password_hash_algorithm', null, 'SHA2_512'), @password))
                set @id = scope_identity()
                set @parameters = dbo.json_set(@parameters, 'id', @id)
            end
        end

    end try
    begin catch
        set @error_code = @error_code | dbo.bw(1, 0)
        execute dbo.exceptions_set @procedure_id, @session, @parameters, @errors
    end catch

    execute dbo.logs_set @procedure_id, @session, @parameters, @errors, 1

end
go

if object_id(N'dbo.sessions_login_guest', N'P') is not null drop procedure dbo.sessions_login_guest
go
create procedure dbo.sessions_login_guest(
    @session integer, 
    @ip varchar(39),
    @errors varchar(512) output, 
    @id integer output
) as begin set nocount on 

    declare @database varchar(64) = db_name()
    declare @procedure_id integer
    declare @machine integer = dbo.machine_session_get()
    declare @parameters varchar(max) = '{"ip":"' + @ip + '","id":null}'
    declare @error_code integer = 0

    set @errors = ''
    execute dbo.procedure_get_id @database, 'sessions_login_guest', @procedure_id output

    begin try
    
        declare @ip_id integer
        declare @ip_error integer
        
        execute dbo.ips_set @procedure_id, @session, @ip, @ip_error output, @ip_id output

        set @error_code = (
            (case when @session is null then 0 else dbo.bw(1, 1) end) | 
            dbo.bw(@ip_error, 2) | 
        0)

        if @error_code = 0 begin 
            insert into dbo.SessionsActived([procedure], [user], [ip]) values (@procedure_id, @machine, @ip_id)
            set @id = scope_identity()
            set @parameters = dbo.json_set(@parameters, 'id', @id)
        end

    end try
    begin catch
        set @error_code = @errors | dbo.bw(1, 0)
        execute dbo.exceptions_set @procedure_id, @machine, @parameters, @errors
    end catch
    
    set @errors = ''

    select [permission] from dbo.SessionsPermissionsView where [session] = @id

    execute dbo.logs_set @procedure_id, @machine, @parameters, @errors, 1

end
go

if object_id(N'dbo.sessions_login', N'P') is not null drop procedure dbo.sessions_login
go
create procedure dbo.sessions_login(
    @session integer, 
    @user varchar(32), 
    @password varchar(256), 
    @ip varchar(39),
    @errors varchar(512) output, 
    @id integer output
) as begin set nocount on 

    declare @database varchar(64) = db_name()
    declare @procedure_id integer
    declare @error_code integer = 0
    declare @parameters varchar(max) = '{' + 
        '"user":"' + @user + '"' + 
        -- ',"password":"' + @password + '"' + 
        ',"ip":"' + @ip + '",' + 
        '"id":null' + 
    '}'

    set @errors = ''
    execute dbo.procedure_get_id @database, 'sessions_login', @procedure_id output

    begin try
    
        declare @ip_id integer
        declare @ip_error integer
        
        execute dbo.ips_set @procedure_id, @session, @ip, @ip_error output, @ip_id output

        set @error_code = (
            dbo.bw(dbo.sessions_validate(@session, 'guest'), 1) | 
            dbo.bw(dbo.sessions_nick_validate(@user), 7) | 
            dbo.bw(dbo.sessions_password_validate(@password), 15) | 
            dbo.bw(@ip_error, 23) |
        0)

        if @error_code = 0 begin 

            declare @user_id integer = (select top 1 id from dbo.Users where nick = @user)

            set @error_code = @error_code | (select top 1 (
                (case when date_out is null then 0 else dbo.bw(1, 13) end) | 
                (case when password = hashbytes(dbo.settings_get('password_hash_algorithm', null, 'SHA2_512'), @password) then 0 else dbo.bw(1, 14) end) | 
            0) from dbo.Users where id = @user_id)

            if @error_code = 0 begin 
                insert into dbo.SessionsActived([procedure], [user], [ip]) values(@procedure_id, @user_id, @ip_id)
                set @id = scope_identity()
                set @parameters = dbo.json_set(@parameters, 'id', @id)
            end
            
        end

    end try
    begin catch
        set @error_code = @errors | dbo.bw(1, 0)
        execute dbo.exceptions_set @procedure_id, @session, @parameters, @errors
    end catch

    execute dbo.logs_set @procedure_id, @session, @parameters, @errors, 1

end
go

if object_id(N'dbo.sessions_logout', N'P') is not null drop procedure dbo.sessions_logout
go
create procedure dbo.sessions_logout(
    @session integer, 
    @errors varchar(512) output, 
    @id integer output
) as begin set nocount on 

    declare @database varchar(64) = db_name()
    declare @procedure_id integer
    declare @error_code integer = 0
    declare @parameters varchar(max) = '{' + 
        '"session":' + convert(varchar(14), @session) + ',' + 
        '"id":null' +
    '}'

    set @errors = ''
    execute dbo.procedure_get_id @database, 'sessions_logout', @procedure_id output

    begin try
    
        set @error_code = (
            dbo.bw(dbo.sessions_validate(@session, ''), 1) | 
        0)

        if @error_code = 0 begin 
            insert into dbo.SessionsActived([procedure], [user], [ip]) select @procedure_id, [user], [ip] from dbo.SessionsActived where id = @session
            set @id = scope_identity()
            delete from dbo.SessionsActived where id = @session
            set @parameters = dbo.json_set(@parameters, 'id', @id)
        end

    end try
    begin catch
        set @error_code = @errors | dbo.bw(1, 0)
        execute dbo.exceptions_set @procedure_id, @session, @parameters, @errors
    end catch

    execute dbo.logs_set @procedure_id, @session, @parameters, @errors, 1

end
go

if object_id(N'dbo.sessions_update', N'P') is not null drop procedure dbo.sessions_update
go
create procedure dbo.sessions_update(
    @session integer, 
    @ip varchar(39),
    @errors varchar(512) output, 
    @id integer output
) as begin set nocount on 

    declare @database varchar(64) = db_name()
    declare @procedure_id integer
    declare @error_code integer = 0
    declare @parameters varchar(max) = '{' + 
        '"session":' + convert(varchar(14), @session) + ',' + 
        '"ip":"' + @ip + '",' + 
        '"id":null' +
    '}'

    set @errors = ''
    execute dbo.procedure_get_id @database, 'sessions_update', @procedure_id output

    begin try

        declare @ip_id integer
        declare @ip_error integer
        
        execute dbo.ips_set @procedure_id, @session, @ip, @ip_error output, @ip_id output

        set @error_code = (
            dbo.bw(dbo.sessions_validate(@session, ''), 1) | 
            dbo.bw(@ip_error, 2) | 
        0)

        if @error_code = 0 
            update dbo.SessionsActived set [ip] = @ip_id, date_last = getdate() where id = @session

    end try
    begin catch
        set @error_code = @errors | dbo.bw(1, 0)
        execute dbo.exceptions_set @procedure_id, @session, @parameters, @errors
    end catch

    execute dbo.logs_set @procedure_id, @session, @parameters, @errors, 8

end
go

if object_id(N'dbo.sessions_autoclose', N'P') is not null drop procedure dbo.sessions_autoclose
go
create procedure dbo.sessions_autoclose as begin set nocount on 
    delete from dbo.SessionsActived where datediff(second, date_last, getdate()) > timeout
end
go

if object_id(N'dbo.sessions_change_password', N'P') is not null drop procedure dbo.sessions_change_password
go
create procedure dbo.sessions_change_password(
    @session integer, 
    @current_password varchar(256),
    @new_password varchar(256),
    @errors varchar(512) output
) as begin set nocount on 

    declare @database varchar(64) = db_name()
    declare @procedure_id integer
    declare @error_code integer = 0
    declare @parameters varchar(max) = '{' + 
        '"session":' + convert(varchar(14), @session) + ',' + 
        '"id":null' +
    '}'

    set @errors = ''
    execute dbo.procedure_get_id @database, 'sessions_change_password', @procedure_id output

    begin try
    
        set @error_code = (
            dbo.bw(dbo.sessions_validate(@session, ''), 1) | 
            dbo.bw(dbo.sessions_password_validate(@current_password), 6) | 
            dbo.bw(dbo.sessions_password_validate(@new_password), 15) | 
            (case when @current_password = @new_password then dbo.bw(1, 23) else 0 end) | 
        0)

        if @error_code = 0 begin 
            if (
                select top 1 0 
                from dbo.SessionsActived sessions 
                join dbo.Users users on sessions.[user] = users.id 
                where 
                    sessions.id = @session and 
                    users.password != hashbytes(dbo.settings_get('password_hash_algorithm', null, 'SHA2_512'), @current_password)
            ) is null 
                set @error_code = @error_code | dbo.bw(1, 14)
            else 
                update users 
                set users.[password] = hashbytes(dbo.settings_get('password_hash_algorithm', null, 'SHA2_512'), @new_password) 
                from dbo.SessionsActived sessions 
                join dbo.Users users on sessions.[user] = users.id 
                where sessions.id = @session
        end

    end try
    begin catch
        set @error_code = @errors | dbo.bw(1, 0)
        execute dbo.exceptions_set @procedure_id, @session, @parameters, @errors
    end catch

    execute dbo.logs_set @procedure_id, @session, @parameters, @errors, 1

end
go