Skip to content

Commit

Permalink
#5 login/register functionality
Browse files Browse the repository at this point in the history
#5 login/register functionality

fix migration tests

#5 login/register functionality
  • Loading branch information
vbilopav committed Jul 14, 2024
1 parent 879ecf5 commit 16604d9
Show file tree
Hide file tree
Showing 26 changed files with 727 additions and 3,392 deletions.
11 changes: 3 additions & 8 deletions backend/cfg/appsettings-server.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,8 @@ It overrides the default configuration in appsettings.json and sets the default
"System": "Warning",
"Microsoft": "Warning"
},
"ToConsole": false,
"ToFile": true,
"FilePath": "./logs/log.txt",
"FileSizeLimitBytes": 500000, // 500 KB
"RetainedFileCountLimit": 5
"ToConsole": true,
"ToFile": false
},

"StaticFiles": {
Expand All @@ -40,11 +37,9 @@ It overrides the default configuration in appsettings.json and sets the default

"NpgsqlRest": {
"ConnectionName": "Default",
"UseEnvironmentConnection": false,
"CommentsMode": "OnlyWithHttpTag",

"LogCommands": false,
"LogCommandParameters": true,

"RequestHeadersMode": "Parameter",
"RequestHeadersParameterName": "_headers",

Expand Down
19 changes: 16 additions & 3 deletions backend/cfg/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/*
Client Build 1.2.8.0
Client Build 1.3.0.0
Npgsql 8.0.3.0
NpgsqlRest 2.8.5.0
NpgsqlRest.HttpFiles 1.0.2.0
NpgsqlRest.TsClient 1.8.1.0
NpgsqlRest.TsClient 1.9.0.0
*/
{
//
Expand Down Expand Up @@ -265,6 +265,15 @@
//
"ConnectionName": null,
//
// If the connection string is not found, empty, or missing host, port or database, the connection string is created from the environment variables.
// See https://www.postgresql.org/docs/current/libpq-envars.html for the list of the environment variables.
//
// Note: Npgsql will use the environment variables by default but only for the small set of the connection string parameters like username and password (see https://www.npgsql.org/doc/connection-string-parameters.html#environment-variables).
// Set this option to true to use environment variables for host, port and database as well.
// When this option is enabled and these environment variables are set, connection string doesn't have to be defined at all and it will be created from the environment variables.
//
"UseEnvironmentConnection": true,
//
// Sets the ApplicationName connection property in the connection string to the value of the ApplicationName configuration.
// Note: This option is ignored if the UseJsonApplicationName option is enabled.
//
Expand Down Expand Up @@ -552,7 +561,11 @@
//
// Array of schema names to skip
//
"SkipSchemas": []
"SkipSchemas": [],
//
// Default TypeScript type for JSON types
//
"DefaultJsonType": "string"
},

//
Expand Down
2 changes: 1 addition & 1 deletion backend/cfg/pgmigrations.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@ module.exports = {
historyTableSchema: "sys",
skipPattern: "scrap",

testFunctionsSchemaContains: "test",
testFunctionsSchemaSimilarTo: "%_tests",
}
22 changes: 22 additions & 0 deletions backend/http/teamserator_db_auth.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@host=https://localhost:5000

// function auth.register_with_password(
// _email text,
// _password text,
// _repeat text
// )
// returns json
//
// comment on function auth.register_with_password is 'HTTP POST /auth/register
// anonymous';
POST {{host}}/auth/register
content-type: application/json

{
"email": "ABC",
"password": "XYZ",
"repeat": "IJK"
}

###

Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
do
$sys$
begin

if not exists(select 1 from pg_roles where rolname = 'teamserator_usr') then
-- development application role
-- for production environment, create a new role with a different password
Expand All @@ -22,17 +21,22 @@ begin
end if;

if not exists(select 1 from information_schema.routines where routine_schema = 'sys' and routine_name = 'check') then
-- # import ./backend/src/sys/sys.check.sql
-- # import ./backend/src/_sys/sys.check.sql
end if;

call sys.check();

if (select current_setting('TimeZone') != 'UTC') then
alter database teamserator_db set timezone to 'UTC';
set time zone 'UTC';
end if;

if not exists(select 1 from information_schema.routines where routine_schema = 'sys' and routine_name = 'drop') then
-- # import ./backend/src/sys/sys.drop.sql
-- # import ./backend/src/_sys/sys.drop.sql
end if;

if not exists(select 1 from information_schema.routines where routine_schema = 'sys' and routine_name = 'annotate') then
-- # import ./backend/src/sys/sys.annotate.sql
-- # import ./backend/src/_sys/sys.annotate.sql
end if;
end;
$sys$;
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,13 @@ as
$$
declare
_db constant text = 'teamserator_db';
_test_schema constant text = 'test';
_user constant text = 'teamserator_usr';
_rec record;
begin
if (current_database() <> _db) then
raise exception 'Database name must be "%" but it is %. Are you sure you are connected to the right database?', _db, current_database();
end if;

if not exists(select 1 from information_schema.schemata where schema_name = _test_schema) then
execute format('create schema %s', _test_schema);
end if;

select * into _rec from pg_roles where rolname = _user;

if _rec is null then
Expand Down
File renamed without changes.
25 changes: 25 additions & 0 deletions backend/src/auth/R__auth.consts.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
call sys.check();
call sys.drop('auth.consts');

create function auth.consts(
_key text
)
returns text
language sql
immutable
parallel safe
as
$$
select case _key
when 'algorithm' then 'bf'
when 'max_attempts' then '5'
when 'lockout_interval' then '5 minutes'
when 'max_password_length' then '72'
when 'min_password_length' then '6'
when 'required_uppercase' then '1'
when 'required_lowercase' then '1'
when 'required_number' then '1'
when 'required_special' then '1'
else null
end;
$$;
116 changes: 116 additions & 0 deletions backend/src/auth/R__auth.login_with_password.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
call sys.check();
call sys.drop('auth.login_with_password_internal');
call sys.drop('auth.login_with_password');

create function auth.login_with_password_internal(
_email text,
_password text,
_now timestamptz
)
returns table (
status int,
name_identifier text,
name text
)
language plpgsql
as
$$
declare
_record record;
_attempt int;
_algorithm constant text = auth.consts('algorithm');
_max_attempts constant int = auth.consts('max_attempts')::int;
_lockout_interval constant interval = auth.consts('lockout_interval')::interval;
begin
if auth.validate_email(_email) is false then
raise info 'Invalid email in logging attempt. %', _email;
return query select 400, null, null;
return;
end if;

select user_id, email, expires_at, active_after, confirmed, password_hash
into _record
from auth.users
where email = _email;

if _record is null then
raise info 'User does not exist in logging attempt. %', _email;
return query select 404, null, null;
return;
end if;

if _record.expires_at is not null and _record.expires_at < _now then
raise info 'User is expired in logging attempt. %', _email;
return query select 423, null, null;
return;
end if;

if _record.active_after is not null and _record.active_after > _now then
raise info 'User is not active in logging attempt. %', _email;
return query select 425, null, null;
return;
end if;

if _record.confirmed is false then
raise info 'User is not confirmed in logging attempt. %', _email;
return query select 403, null, null;
return;
end if;

if _record.password_hash is null then
raise info 'User does not have a password in logging attempt. %', _email;
return query select 406, null, null;
return;
end if;

if crypt(_password, _record.password_hash) <> _record.password_hash then
update auth.users
set password_attempts = password_attempts + 1
where user_id = _record.user_id
returning password_attempts
into _attempt;

if _attempt >= _max_attempts then
update auth.users
set active_after = _now + _lockout_interval
where user_id = _record.user_id;

raise info 'User % is locked out in logging attempt until %', _email, _now + _lockout_interval;
return query select 423, null, null;
return;
end if;

raise info 'Invalid password in logging attempt. %', _email;
return query select 401, null, null;
return;
end if;

update auth.users
set
password_attempts = 0,
password_hash = crypt(_password, gen_salt(_algorithm))
where
user_id = _record.user_id;

return query
select 200, _record.user_id::text, _email;
end
$$;

create function auth.login_with_password(
_email text,
_password text
)
returns table (
status int,
name_identifier text,
name text
)
language sql as 'select status, name_identifier, name from auth.login_with_password_internal(_email, _password, now());';


call sys.annotate(
'auth.login_with_password',
'HTTP POST /auth/login',
'anonymous'
);
Loading

0 comments on commit 16604d9

Please sign in to comment.