From 42884550dfc79f52c03c49dacaa8bf0c06da65eb Mon Sep 17 00:00:00 2001 From: Kraiem Taha Yassine Date: Tue, 27 Jul 2021 14:37:45 +0200 Subject: [PATCH] Api invitation link (#105) * feat(api): invitation token to add team members * feat(api): invitation link change password * feat(db): changed base_auth structure * feat(api): invitation link - regenerate/reset * feat(api): invitation link - restore deleted user * feat(api): invitation link for forget password * feat(api): changed email body for invite user and reset password --- api/.chalice/config.json | 2 + api/chalicelib/blueprints/app/__init__.py | 0 api/chalicelib/blueprints/bp_core.py | 9 +- api/chalicelib/blueprints/bp_core_crons.py | 5 - api/chalicelib/blueprints/bp_core_dynamic.py | 37 +++++ api/chalicelib/core/reset_password.py | 49 ++---- api/chalicelib/core/users.py | 155 +++++++++++++----- api/chalicelib/utils/email_helper.py | 10 +- api/chalicelib/utils/html/invitation.html | 26 +-- api/chalicelib/utils/html/reset_password.html | 4 +- .../db/init_dbs/postgresql/1.3.0/1.3.0.sql | 24 ++- .../db/init_dbs/postgresql/init_schema.sql | 30 ++-- .../db/init_dbs/postgresql/1.3.0/1.3.0.sql | 35 ++-- .../db/init_dbs/postgresql/init_schema.sql | 34 ++-- 14 files changed, 253 insertions(+), 167 deletions(-) create mode 100644 api/chalicelib/blueprints/app/__init__.py diff --git a/api/.chalice/config.json b/api/.chalice/config.json index 8dd829be6..66c7f3fa3 100644 --- a/api/.chalice/config.json +++ b/api/.chalice/config.json @@ -52,6 +52,8 @@ "S3_HOST": "", "S3_KEY": "", "S3_SECRET": "", + "invitation_link": "/api/users/invitation?token=%s", + "change_password_link": "/changepassword?invitation=%s&&pass=%s", "version_number": "1.2.0" }, "lambda_timeout": 150, diff --git a/api/chalicelib/blueprints/app/__init__.py b/api/chalicelib/blueprints/app/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/api/chalicelib/blueprints/bp_core.py b/api/chalicelib/blueprints/bp_core.py index f4a28c048..62b4ec114 100644 --- a/api/chalicelib/blueprints/bp_core.py +++ b/api/chalicelib/blueprints/bp_core.py @@ -509,8 +509,8 @@ def reset_password_handler(step): if "email" not in data or len(data["email"]) < 5: return {"errors": ["please provide a valid email address"]} return reset_password.step1(data) - elif step == "2": - return reset_password.step2(data) + # elif step == "2": + # return reset_password.step2(data) @app.route('/{projectId}/metadata', methods=['GET']) @@ -587,9 +587,8 @@ def async_basic_emails(step): if data.pop("auth") != environ["async_Token"]: return {} if step.lower() == "member_invitation": - email_helper.send_team_invitation(recipient=data["email"], user_name=data["userName"], - temp_password=data["tempPassword"], client_id=data["clientId"], - sender_name=data["senderName"]) + email_helper.send_team_invitation(recipient=data["email"], invitation_link=data["invitationLink"], + client_id=data["clientId"], sender_name=data["senderName"]) @app.route('/{projectId}/sample_rate', methods=['GET']) diff --git a/api/chalicelib/blueprints/bp_core_crons.py b/api/chalicelib/blueprints/bp_core_crons.py index b8e97b19b..817ffbb16 100644 --- a/api/chalicelib/blueprints/bp_core_crons.py +++ b/api/chalicelib/blueprints/bp_core_crons.py @@ -12,11 +12,6 @@ def run_scheduled_jobs(event): jobs.execute_jobs() -@app.schedule(Cron('0/60', '*', '*', '*', '?', '*')) -def clear_password_reset(event): - reset_password.cron() - - # Run every monday. @app.schedule(Cron('5', '0', '?', '*', 'MON', '*')) def weekly_report2(event): diff --git a/api/chalicelib/blueprints/bp_core_dynamic.py b/api/chalicelib/blueprints/bp_core_dynamic.py index 6f8df99df..108de050c 100644 --- a/api/chalicelib/blueprints/bp_core_dynamic.py +++ b/api/chalicelib/blueprints/bp_core_dynamic.py @@ -356,6 +356,38 @@ def add_member(context): return users.create_member(tenant_id=context['tenantId'], user_id=context['userId'], data=data) +@app.route('/users/invitation', methods=['GET'], authorizer=None) +def process_invitation_link(): + params = app.current_request.query_params + if params is None or len(params.get("token", "")) < 64: + return {"errors": ["please provide a valid invitation"]} + user = users.get_by_invitation_token(params["token"]) + if user is None: + return {"errors": ["invitation not found"]} + if user["expired"]: + return {"errors": ["expired invitation, please ask your admin to send a new one"]} + pass_token = users.allow_password_change(user_id=user["userId"]) + return Response( + status_code=307, + body='', + headers={'Location': environ["SITE_URL"] + environ["change_password_link"] % (params["token"], pass_token), + 'Content-Type': 'text/plain'}) + + +@app.route('/users/invitation/password', methods=['POST', 'PUT'], authorizer=None) +def change_password_by_invitation(): + data = app.current_request.json_body + if data is None or len(data.get("invitation", "")) < 64 or len(data.get("pass", "")) < 8: + return {"errors": ["please provide a valid invitation & pass"]} + user = users.get_by_invitation_token(token=data["token"], pass_token=data["pass"]) + if user is None: + return {"errors": ["invitation not found"]} + if user["expiredChange"]: + return {"errors": ["expired change, please re-use the invitation link"]} + + return users.set_password_invitation(new_password=data["password"], user_id=user["userId"]) + + @app.route('/client/members/{memberId}', methods=['PUT', 'POST']) def edit_member(memberId, context): data = app.current_request.json_body @@ -363,6 +395,11 @@ def edit_member(memberId, context): user_id_to_update=memberId) +@app.route('/client/members/{memberId}/reset', methods=['GET']) +def reset_reinvite_member(memberId, context): + return users.reset_member(tenant_id=context['tenantId'], editor_id=context['userId'], user_id_to_update=memberId) + + @app.route('/client/members/{memberId}', methods=['DELETE']) def delete_member(memberId, context): return users.delete_member(tenant_id=context["tenantId"], user_id=context['userId'], id_to_delete=memberId) diff --git a/api/chalicelib/core/reset_password.py b/api/chalicelib/core/reset_password.py index 4d4c3939e..0b677c8e6 100644 --- a/api/chalicelib/core/reset_password.py +++ b/api/chalicelib/core/reset_password.py @@ -18,48 +18,23 @@ def step1(data): a_users = users.get_by_email_only(data["email"]) if len(a_users) > 1: print(f"multiple users found for [{data['email']}] please contact our support") - return {"errors": ["please contact our support"]} + return {"errors": ["multiple users, please contact our support"]} elif len(a_users) == 1: a_users = a_users[0] - reset_token = secrets.token_urlsafe(6) - users.update(tenant_id=a_users["tenantId"], user_id=a_users["id"], - changes={"token": reset_token}) - email_helper.send_reset_code(recipient=data["email"], reset_code=reset_token) + invitation_link=users.generate_new_invitation(user_id=a_users["id"]) + email_helper.send_forgot_password(recipient=data["email"], invitation_link=invitation_link) else: print(f"invalid email address [{data['email']}]") return {"errors": ["invalid email address"]} return {"data": {"state": "success"}} -def step2(data): - print("====================== change password 2 ===============") - user = users.get_by_email_reset(data["email"], data["code"]) - if not user: - print("error: wrong email or reset code") - return {"errors": ["wrong email or reset code"]} - users.update(tenant_id=user["tenantId"], user_id=user["id"], - changes={"token": None, "password": data["password"], "generatedPassword": False}) - return {"data": {"state": "success"}} - - -def cron(): - with pg_client.PostgresClient() as cur: - cur.execute( - cur.mogrify("""\ - SELECT user_id - FROM public.basic_authentication - WHERE token notnull - AND (token_requested_at isnull or (EXTRACT(EPOCH FROM token_requested_at)*1000)::BIGINT < %(time)s);""", - {"time": chalicelib.utils.TimeUTC.TimeUTC.now(delta_days=-1)}) - ) - results = cur.fetchall() - if len(results) == 0: - return - results = tuple([r["user_id"] for r in results]) - cur.execute( - cur.mogrify("""\ - UPDATE public.basic_authentication - SET token = NULL, token_requested_at = NULL - WHERE user_id in %(ids)s;""", - {"ids": results}) - ) +# def step2(data): +# print("====================== change password 2 ===============") +# user = users.get_by_email_reset(data["email"], data["code"]) +# if not user: +# print("error: wrong email or reset code") +# return {"errors": ["wrong email or reset code"]} +# users.update(tenant_id=user["tenantId"], user_id=user["id"], +# changes={"token": None, "password": data["password"], "generatedPassword": False}) +# return {"data": {"state": "success"}} diff --git a/api/chalicelib/core/users.py b/api/chalicelib/core/users.py index b4166d847..5f8871783 100644 --- a/api/chalicelib/core/users.py +++ b/api/chalicelib/core/users.py @@ -9,20 +9,30 @@ from chalicelib.utils.TimeUTC import TimeUTC from chalicelib.utils.helper import environ from chalicelib.core import tenants +import secrets -def create_new_member(email, password, admin, name, owner=False): +def __generate_invitation_token(): + return secrets.token_urlsafe(64) + + +def __is_authorized_to_manage_users(): + pass + + +def create_new_member(email, invitation_token, admin, name, owner=False): with pg_client.PostgresClient() as cur: query = cur.mogrify(f"""\ - WITH u AS ( - INSERT INTO public.users (email, role, name, data) - VALUES (%(email)s, %(role)s, %(name)s, %(data)s) - RETURNING user_id,email,role,name,appearance - ), - au AS (INSERT - INTO public.basic_authentication (user_id, password, generated_password) - VALUES ((SELECT user_id FROM u), crypt(%(password)s, gen_salt('bf', 12)), TRUE)) - SELECT u.user_id AS id, + WITH u AS (INSERT INTO public.users (email, role, name, data) + VALUES (%(email)s, %(role)s, %(name)s, %(data)s) + RETURNING user_id,email,role,name,appearance + ), + au AS (INSERT INTO public.basic_authentication (user_id, generated_password, invitation_token, invited_at) + VALUES ((SELECT user_id FROM u), TRUE, %(invitation_token)s, timezone('utc'::text, now())) + RETURNING invitation_token + ) + SELECT u.user_id, + u.user_id AS id, u.email, u.role, u.name, @@ -30,18 +40,19 @@ def create_new_member(email, password, admin, name, owner=False): (CASE WHEN u.role = 'owner' THEN TRUE ELSE FALSE END) AS super_admin, (CASE WHEN u.role = 'admin' THEN TRUE ELSE FALSE END) AS admin, (CASE WHEN u.role = 'member' THEN TRUE ELSE FALSE END) AS member, - u.appearance - FROM u;""", - {"email": email, "password": password, + au.invitation_token + FROM u,au;""", + {"email": email, "password": invitation_token, "role": "owner" if owner else "admin" if admin else "member", "name": name, - "data": json.dumps({"lastAnnouncementView": TimeUTC.now()})}) + "data": json.dumps({"lastAnnouncementView": TimeUTC.now()}), + "invitation_token": invitation_token}) cur.execute( query ) return helper.dict_to_camel_case(cur.fetchone()) -def restore_member(user_id, email, password, admin, name, owner=False): +def restore_member(user_id, email, invitation_token, admin, name, owner=False): with pg_client.PostgresClient() as cur: query = cur.mogrify(f"""\ UPDATE public.users @@ -58,31 +69,61 @@ def restore_member(user_id, email, password, admin, name, owner=False): TRUE AS change_password, (CASE WHEN role = 'owner' THEN TRUE ELSE FALSE END) AS super_admin, (CASE WHEN role = 'admin' THEN TRUE ELSE FALSE END) AS admin, - (CASE WHEN role = 'member' THEN TRUE ELSE FALSE END) AS member, - appearance;""", + (CASE WHEN role = 'member' THEN TRUE ELSE FALSE END) AS member;""", {"user_id": user_id, "email": email, "role": "owner" if owner else "admin" if admin else "member", "name": name}) cur.execute( query ) - result = helper.dict_to_camel_case(cur.fetchone()) + result = cur.fetchone() query = cur.mogrify("""\ UPDATE public.basic_authentication - SET password= crypt(%(password)s, gen_salt('bf', 12)), - generated_password= TRUE, - token=NULL, - token_requested_at=NULL - WHERE user_id=%(user_id)s;""", - {"user_id": user_id, "password": password}) + SET generated_password = TRUE, + invitation_token = %(invitation_token)s, + invited_at = timezone('utc'::text, now()), + change_pwd_expire_at = NULL, + change_pwd_token = NULL + WHERE user_id=%(user_id)s + RETURNING invitation_token;""", + {"user_id": user_id, "invitation_token": invitation_token}) cur.execute( query ) + result["invitation_token"] = cur.fetchone()["invitation_token"] - return result + return helper.dict_to_camel_case(result) + + +def generate_new_invitation(user_id): + invitation_token = __generate_invitation_token() + with pg_client.PostgresClient() as cur: + query = cur.mogrify("""\ + UPDATE public.basic_authentication + SET invitation_token = %(invitation_token)s, + invited_at = timezone('utc'::text, now()), + change_pwd_expire_at = NULL, + change_pwd_token = NULL + WHERE user_id=%(user_id)s + RETURNING invitation_token;""", + {"user_id": user_id, "invitation_token": invitation_token}) + cur.execute( + query + ) + return __get_invitation_link(cur.fetchone().pop("invitation_token")) + + +def reset_member(tenant_id, editor_id, user_id_to_update): + admin = get(tenant_id=tenant_id, user_id=editor_id) + if not admin["admin"] and not admin["superAdmin"]: + return {"errors": ["unauthorized"]} + user = get(tenant_id=tenant_id, user_id=user_id_to_update) + if not user: + return {"errors": ["user not found"]} + return {"data": {"invitationLink": generate_new_invitation(user_id_to_update)}} def update(tenant_id, user_id, changes): - AUTH_KEYS = ["password", "generatedPassword", "token"] + AUTH_KEYS = ["password", "generatedPassword", "invitationToken", "invitedAt", "changePwdExpireAt", "changePwdToken"] if len(changes.keys()) == 0: return None @@ -93,13 +134,6 @@ def update(tenant_id, user_id, changes): if key == "password": sub_query_bauth.append("password = crypt(%(password)s, gen_salt('bf', 12))") sub_query_bauth.append("changed_at = timezone('utc'::text, now())") - elif key == "token": - if changes[key] is not None: - sub_query_bauth.append("token = %(token)s") - sub_query_bauth.append("token_requested_at = timezone('utc'::text, now())") - else: - sub_query_bauth.append("token = NULL") - sub_query_bauth.append("token_requested_at = NULL") else: sub_query_bauth.append(f"{helper.key_to_snake_case(key)} = %({key})s") else: @@ -166,26 +200,43 @@ def create_member(tenant_id, user_id, data): return {"errors": ["invalid user name"]} if name is None: name = data["email"] - temp_pass = helper.generate_salt()[:8] + invitation_token = __generate_invitation_token() user = get_deleted_user_by_email(email=data["email"]) if user is not None: - new_member = restore_member(email=data["email"], password=temp_pass, + new_member = restore_member(email=data["email"], invitation_token=invitation_token, admin=data.get("admin", False), name=name, user_id=user["userId"]) else: - new_member = create_new_member(email=data["email"], password=temp_pass, + new_member = create_new_member(email=data["email"], invitation_token=invitation_token, admin=data.get("admin", False), name=name) - + new_member["invitationLink"] = __get_invitation_link(new_member.pop("invitationToken")) helper.async_post(environ['email_basic'] % 'member_invitation', { "email": data["email"], - "userName": data["email"], - "tempPassword": temp_pass, + "invitationLink": new_member["invitationLink"], "clientId": tenants.get_by_tenant_id(tenant_id)["name"], "senderName": admin["name"] }) return {"data": new_member} +def __get_invitation_link(invitation_token): + return environ["SITE_URL"] + environ["invitation_link"] % invitation_token + + +def allow_password_change(user_id, delta_min=10): + pass_token = secrets.token_urlsafe(8) + with pg_client.PostgresClient() as cur: + query = cur.mogrify(f"""UPDATE public.basic_authentication + SET change_pwd_expire_at = timezone('utc'::text, now()+INTERVAL '%(delta)s MINUTES'), + change_pwd_token = %(pass_token)s + WHERE user_id = %(user_id)s""", + {"user_id": user_id, "delta": delta_min, "pass_token": pass_token}) + cur.execute( + query + ) + return pass_token + + def get(user_id, tenant_id): with pg_client.PostgresClient() as cur: cur.execute( @@ -367,6 +418,15 @@ def change_password(tenant_id, user_id, email, old_password, new_password): "jwt": authenticate(email, new_password)["jwt"]} +def set_password_invitation(user_id, new_password): + changes = {"password": new_password, "generatedPassword": False, + "invitationToken": None, "invitedAt": None, + "changePwdExpireAt": None, "changePwdToken": None} + user = update(tenant_id=-1, user_id=user_id, changes=changes) + return {"data": user, + "jwt": authenticate(user["email"], new_password)["jwt"]} + + def count_members(): with pg_client.PostgresClient() as cur: cur.execute("""SELECT COUNT(user_id) @@ -409,6 +469,23 @@ def get_deleted_user_by_email(email): return helper.dict_to_camel_case(r) +def get_by_invitation_token(token, pass_token=None): + with pg_client.PostgresClient() as cur: + cur.execute( + cur.mogrify( + f"""SELECT + *, + DATE_PART('day',timezone('utc'::text, now())- invited_at)>=1 AS expired, + change_pwd_expire_at <= timezone('utc'::text, now()) AS expired_change + FROM public.users INNER JOIN public.basic_authentication USING(user_id) + WHERE invitation_token = %(token)s {"AND change_pwd_token = %(pass_token)s" if pass_token else ""} + LIMIT 1;""", + {"token": token, "pass_token": token}) + ) + r = cur.fetchone() + return helper.dict_to_camel_case(r) + + def auth_exists(user_id, tenant_id, jwt_iat, jwt_aud): with pg_client.PostgresClient() as cur: cur.execute( diff --git a/api/chalicelib/utils/email_helper.py b/api/chalicelib/utils/email_helper.py index 8afc84d02..72072c924 100644 --- a/api/chalicelib/utils/email_helper.py +++ b/api/chalicelib/utils/email_helper.py @@ -2,18 +2,18 @@ from chalicelib.utils.TimeUTC import TimeUTC from chalicelib.utils.email_handler import __get_html_from_file, send_html, __escape_text_html -def send_team_invitation(recipient, user_name, temp_password, client_id, sender_name): +def send_team_invitation(recipient, client_id, sender_name, invitation_link): BODY_HTML = __get_html_from_file("chalicelib/utils/html/invitation.html", - formatting_variables={"userName": __escape_text_html(user_name), - "password": temp_password, "clientId": client_id, + formatting_variables={"invitationLink": invitation_link, + "clientId": client_id, "sender": sender_name}) SUBJECT = "Welcome to OpenReplay" send_html(BODY_HTML, SUBJECT, recipient) -def send_reset_code(recipient, reset_code): +def send_forgot_password(recipient, invitation_link): BODY_HTML = __get_html_from_file("chalicelib/utils/html/reset_password.html", - formatting_variables={"code": reset_code}) + formatting_variables={"invitationLink": invitation_link}) SUBJECT = "Password recovery" send_html(BODY_HTML, SUBJECT, recipient) diff --git a/api/chalicelib/utils/html/invitation.html b/api/chalicelib/utils/html/invitation.html index 079d8b824..471cb3dd3 100644 --- a/api/chalicelib/utils/html/invitation.html +++ b/api/chalicelib/utils/html/invitation.html @@ -447,10 +447,10 @@ width: 25%!important

Please use this link to login:

- %(frontend_url)s%(invitationLink)s

@@ -485,40 +485,18 @@ width: 25%!important -
-
-

- Your login credentials

-
-
-
-
-

- Username / Email

-

- %(userName)s

-
-
-
-
-

- Password

-

- %(password)s

-
-

- Use the code below to reset your password (valid for 24 hours only):

+ Use the link below to reset your password (valid for 24 hours only):


- %(code)s%(invitationLink)s

diff --git a/ee/scripts/helm/db/init_dbs/postgresql/1.3.0/1.3.0.sql b/ee/scripts/helm/db/init_dbs/postgresql/1.3.0/1.3.0.sql index eaaa9c0e0..e475bd41f 100644 --- a/ee/scripts/helm/db/init_dbs/postgresql/1.3.0/1.3.0.sql +++ b/ee/scripts/helm/db/init_dbs/postgresql/1.3.0/1.3.0.sql @@ -1,14 +1,22 @@ BEGIN; CREATE INDEX sessions_session_id_project_id_start_ts_durationNN_idx ON sessions (session_id, project_id, start_ts) WHERE duration IS NOT NULL; -CREATE INDEX clicks_label_session_id_timestamp_idx ON events.clicks (label,session_id,timestamp); -CREATE INDEX pages_base_path_session_id_timestamp_idx ON events.pages (base_path,session_id,timestamp); -CREATE INDEX ON unstarted_sessions(project_id); -CREATE INDEX ON assigned_sessions(session_id); -CREATE INDEX ON technical_info(session_id); -CREATE INDEX inputs_label_session_id_timestamp_idx ON events.inputs (label,session_id,timestamp); +CREATE INDEX clicks_label_session_id_timestamp_idx ON events.clicks (label, session_id, timestamp); +CREATE INDEX pages_base_path_session_id_timestamp_idx ON events.pages (base_path, session_id, timestamp); +CREATE INDEX ON unstarted_sessions (project_id); +CREATE INDEX ON assigned_sessions (session_id); +CREATE INDEX ON technical_info (session_id); +CREATE INDEX inputs_label_session_id_timestamp_idx ON events.inputs (label, session_id, timestamp); CREATE INDEX clicks_url_idx ON events.clicks (url); CREATE INDEX clicks_url_gin_idx ON events.clicks USING GIN (url gin_trgm_ops); -CREATE INDEX clicks_url_session_id_timestamp_selector_idx ON events.clicks (url, session_id, timestamp,selector); +CREATE INDEX clicks_url_session_id_timestamp_selector_idx ON events.clicks (url, session_id, timestamp, selector); -COMMIT ; \ No newline at end of file +ALTER TABLE public.basic_authentication + RENAME COLUMN token TO invitation_token; +ALTER TABLE public.basic_authentication + RENAME COLUMN token_requested_at TO invited_at; +ALTER TABLE public.basic_authentication + ADD COLUMN change_pwd_expire_at timestamp without time zone NULL DEFAULT NULL; +ALTER TABLE basic_authentication + ADD COLUMN change_pwd_token text NULL DEFAULT NULL; +COMMIT; \ No newline at end of file diff --git a/ee/scripts/helm/db/init_dbs/postgresql/init_schema.sql b/ee/scripts/helm/db/init_dbs/postgresql/init_schema.sql index 30c66a4dd..8133720ad 100644 --- a/ee/scripts/helm/db/init_dbs/postgresql/init_schema.sql +++ b/ee/scripts/helm/db/init_dbs/postgresql/init_schema.sql @@ -60,7 +60,7 @@ CREATE TABLE users "role": "dev", "dashboard": { "cpu": true, - "fps": false, + "fps": false, "avgCpu": true, "avgFps": true, "errors": true, @@ -121,19 +121,21 @@ CREATE TABLE users jwt_iat timestamp without time zone NULL DEFAULT NULL, data jsonb NOT NULL DEFAULT '{}'::jsonb, weekly_report boolean NOT NULL DEFAULT TRUE, - origin user_origin NULL DEFAULT NULL, - + origin user_origin NULL DEFAULT NULL, + ); CREATE TABLE basic_authentication ( - user_id integer NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, - password text DEFAULT NULL, - generated_password boolean NOT NULL DEFAULT false, - token text NULL DEFAULT NULL, - token_requested_at timestamp without time zone NULL DEFAULT NULL, - changed_at timestamp, + user_id integer NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, + password text DEFAULT NULL, + generated_password boolean NOT NULL DEFAULT false, + invitation_token text NULL DEFAULT NULL, + invited_at timestamp without time zone NULL DEFAULT NULL, + change_pwd_token text NULL DEFAULT NULL, + change_pwd_expire_at timestamp without time zone NULL DEFAULT NULL, + changed_at timestamp, UNIQUE (user_id) ); @@ -576,7 +578,7 @@ create table assigned_sessions created_at timestamp default timezone('utc'::text, now()) NOT NULL, provider_data jsonb default '{}'::jsonb NOT NULL ); -CREATE INDEX ON assigned_sessions(session_id); +CREATE INDEX ON assigned_sessions (session_id); -- --- events_common.sql --- @@ -680,7 +682,7 @@ CREATE INDEX pages_path_idx ON events.pages (path); CREATE INDEX pages_visually_complete_idx ON events.pages (visually_complete) WHERE visually_complete > 0; CREATE INDEX pages_dom_building_time_idx ON events.pages (dom_building_time) WHERE dom_building_time > 0; CREATE INDEX pages_load_time_idx ON events.pages (load_time) WHERE load_time > 0; -CREATE INDEX pages_base_path_session_id_timestamp_idx ON events.pages (base_path,session_id,timestamp); +CREATE INDEX pages_base_path_session_id_timestamp_idx ON events.pages (base_path, session_id, timestamp); CREATE TABLE events.clicks @@ -695,10 +697,10 @@ CREATE INDEX ON events.clicks (session_id); CREATE INDEX ON events.clicks (label); CREATE INDEX clicks_label_gin_idx ON events.clicks USING GIN (label gin_trgm_ops); CREATE INDEX ON events.clicks (timestamp); -CREATE INDEX clicks_label_session_id_timestamp_idx ON events.clicks (label,session_id,timestamp); +CREATE INDEX clicks_label_session_id_timestamp_idx ON events.clicks (label, session_id, timestamp); CREATE INDEX clicks_url_idx ON events.clicks (url); CREATE INDEX clicks_url_gin_idx ON events.clicks USING GIN (url gin_trgm_ops); -CREATE INDEX clicks_url_session_id_timestamp_selector_idx ON events.clicks (url, session_id, timestamp,selector); +CREATE INDEX clicks_url_session_id_timestamp_selector_idx ON events.clicks (url, session_id, timestamp, selector); CREATE TABLE events.inputs @@ -715,7 +717,7 @@ CREATE INDEX ON events.inputs (label, value); CREATE INDEX inputs_label_gin_idx ON events.inputs USING GIN (label gin_trgm_ops); CREATE INDEX inputs_label_idx ON events.inputs (label); CREATE INDEX ON events.inputs (timestamp); -CREATE INDEX inputs_label_session_id_timestamp_idx ON events.inputs (label,session_id,timestamp); +CREATE INDEX inputs_label_session_id_timestamp_idx ON events.inputs (label, session_id, timestamp); CREATE TABLE events.errors ( diff --git a/scripts/helm/db/init_dbs/postgresql/1.3.0/1.3.0.sql b/scripts/helm/db/init_dbs/postgresql/1.3.0/1.3.0.sql index e4c030457..400645fb3 100644 --- a/scripts/helm/db/init_dbs/postgresql/1.3.0/1.3.0.sql +++ b/scripts/helm/db/init_dbs/postgresql/1.3.0/1.3.0.sql @@ -1,19 +1,30 @@ BEGIN; CREATE INDEX sessions_session_id_project_id_start_ts_durationNN_idx ON sessions (session_id, project_id, start_ts) WHERE duration IS NOT NULL; -CREATE INDEX clicks_label_session_id_timestamp_idx ON events.clicks (label,session_id,timestamp); -CREATE INDEX pages_base_path_session_id_timestamp_idx ON events.pages (base_path,session_id,timestamp); -CREATE INDEX ON unstarted_sessions(project_id); -CREATE INDEX ON assigned_sessions(session_id); -CREATE INDEX ON technical_info(session_id); -CREATE INDEX inputs_label_session_id_timestamp_idx ON events.inputs (label,session_id,timestamp); +CREATE INDEX clicks_label_session_id_timestamp_idx ON events.clicks (label, session_id, timestamp); +CREATE INDEX pages_base_path_session_id_timestamp_idx ON events.pages (base_path, session_id, timestamp); +CREATE INDEX ON unstarted_sessions (project_id); +CREATE INDEX ON assigned_sessions (session_id); +CREATE INDEX ON technical_info (session_id); +CREATE INDEX inputs_label_session_id_timestamp_idx ON events.inputs (label, session_id, timestamp); -ALTER TABLE events.clicks ADD COLUMN - url text DEFAULT '' NOT NULL; -ALTER TABLE events.clicks ADD COLUMN - selector text DEFAULT '' NOT NULL; +ALTER TABLE events.clicks + ADD COLUMN + url text DEFAULT '' NOT NULL; +ALTER TABLE events.clicks + ADD COLUMN + selector text DEFAULT '' NOT NULL; CREATE INDEX clicks_url_idx ON events.clicks (url); CREATE INDEX clicks_url_gin_idx ON events.clicks USING GIN (url gin_trgm_ops); -CREATE INDEX clicks_url_session_id_timestamp_selector_idx ON events.clicks (url, session_id, timestamp,selector); +CREATE INDEX clicks_url_session_id_timestamp_selector_idx ON events.clicks (url, session_id, timestamp, selector); -COMMIT ; \ No newline at end of file + +ALTER TABLE public.basic_authentication + RENAME COLUMN token TO invitation_token; +ALTER TABLE public.basic_authentication + RENAME COLUMN token_requested_at TO invited_at; +ALTER TABLE public.basic_authentication + ADD COLUMN change_pwd_expire_at timestamp without time zone NULL DEFAULT NULL; +ALTER TABLE basic_authentication + ADD COLUMN change_pwd_token text NULL DEFAULT NULL; +COMMIT; \ No newline at end of file diff --git a/scripts/helm/db/init_dbs/postgresql/init_schema.sql b/scripts/helm/db/init_dbs/postgresql/init_schema.sql index d02e84375..6a899f398 100644 --- a/scripts/helm/db/init_dbs/postgresql/init_schema.sql +++ b/scripts/helm/db/init_dbs/postgresql/init_schema.sql @@ -122,12 +122,14 @@ CREATE TABLE users CREATE TABLE basic_authentication ( - user_id integer NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, - password text DEFAULT NULL, - generated_password boolean NOT NULL DEFAULT false, - token text NULL DEFAULT NULL, - token_requested_at timestamp without time zone NULL DEFAULT NULL, - changed_at timestamp, + user_id integer NOT NULL REFERENCES users (user_id) ON DELETE CASCADE, + password text DEFAULT NULL, + generated_password boolean NOT NULL DEFAULT false, + invitation_token text NULL DEFAULT NULL, + invited_at timestamp without time zone NULL DEFAULT NULL, + change_pwd_token text NULL DEFAULT NULL, + change_pwd_expire_at timestamp without time zone NULL DEFAULT NULL, + changed_at timestamp, UNIQUE (user_id) ); @@ -558,7 +560,7 @@ create table assigned_sessions created_at timestamp default timezone('utc'::text, now()) NOT NULL, provider_data jsonb default '{}'::jsonb NOT NULL ); -CREATE INDEX ON assigned_sessions(session_id); +CREATE INDEX ON assigned_sessions (session_id); -- --- events_common.sql --- @@ -674,27 +676,27 @@ CREATE INDEX pages_timestamp_metgt0_idx ON events.pages (timestamp) WHERE respon time_to_interactive > 0; CREATE INDEX pages_session_id_speed_indexgt0nn_idx ON events.pages (session_id, speed_index) WHERE speed_index > 0 AND speed_index IS NOT NULL; CREATE INDEX pages_session_id_timestamp_dom_building_timegt0nn_idx ON events.pages (session_id, timestamp, dom_building_time) WHERE dom_building_time > 0 AND dom_building_time IS NOT NULL; -CREATE INDEX pages_base_path_session_id_timestamp_idx ON events.pages (base_path,session_id,timestamp); +CREATE INDEX pages_base_path_session_id_timestamp_idx ON events.pages (base_path, session_id, timestamp); CREATE TABLE events.clicks ( - session_id bigint NOT NULL REFERENCES sessions (session_id) ON DELETE CASCADE, - message_id bigint NOT NULL, - timestamp bigint NOT NULL, + session_id bigint NOT NULL REFERENCES sessions (session_id) ON DELETE CASCADE, + message_id bigint NOT NULL, + timestamp bigint NOT NULL, label text DEFAULT NULL, url text DEFAULT '' NOT NULL; - selector text DEFAULT '' NOT NULL; - PRIMARY KEY (session_id, message_id) +selector text DEFAULT '' NOT NULL; +PRIMARY KEY (session_id, message_id) ); CREATE INDEX ON events.clicks (session_id); CREATE INDEX ON events.clicks (label); CREATE INDEX clicks_label_gin_idx ON events.clicks USING GIN (label gin_trgm_ops); CREATE INDEX ON events.clicks (timestamp); -CREATE INDEX clicks_label_session_id_timestamp_idx ON events.clicks (label,session_id,timestamp); +CREATE INDEX clicks_label_session_id_timestamp_idx ON events.clicks (label, session_id, timestamp); CREATE INDEX clicks_url_idx ON events.clicks (url); CREATE INDEX clicks_url_gin_idx ON events.clicks USING GIN (url gin_trgm_ops); -CREATE INDEX clicks_url_session_id_timestamp_selector_idx ON events.clicks (url, session_id, timestamp,selector); +CREATE INDEX clicks_url_session_id_timestamp_selector_idx ON events.clicks (url, session_id, timestamp, selector); CREATE TABLE events.inputs @@ -711,7 +713,7 @@ CREATE INDEX ON events.inputs (label, value); CREATE INDEX inputs_label_gin_idx ON events.inputs USING GIN (label gin_trgm_ops); CREATE INDEX inputs_label_idx ON events.inputs (label); CREATE INDEX ON events.inputs (timestamp); -CREATE INDEX inputs_label_session_id_timestamp_idx ON events.inputs (label,session_id,timestamp); +CREATE INDEX inputs_label_session_id_timestamp_idx ON events.inputs (label, session_id, timestamp); CREATE TABLE events.errors (