diff --git a/config/sql/session_ids.sql b/config/sql/session_ids.sql new file mode 100644 index 00000000..24491405 --- /dev/null +++ b/config/sql/session_ids.sql @@ -0,0 +1,24 @@ +-- Table: public.session_ids + +-- DROP TABLE public.session_ids; + +CREATE TABLE public.session_ids +( + id text NOT NULL, + email text, + issued timestamp with time zone, + CONSTRAINT session_ids_pkey PRIMARY KEY (id) +); + +GRANT ALL ON TABLE public.channel_videos TO kemal; + +-- Index: public.session_ids_id_idx + +-- DROP INDEX public.session_ids_id_idx; + +CREATE INDEX session_ids_id_idx + ON public.session_ids + USING btree + (id COLLATE pg_catalog."default"); + + \ No newline at end of file diff --git a/config/sql/users.sql b/config/sql/users.sql index f806271c..536508a4 100644 --- a/config/sql/users.sql +++ b/config/sql/users.sql @@ -4,7 +4,6 @@ CREATE TABLE public.users ( - id text[] NOT NULL, updated timestamp with time zone, notifications text[], subscriptions text[], diff --git a/src/invidious.cr b/src/invidious.cr index 28d3fbf5..169c4f12 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -163,9 +163,10 @@ before_all do |env| # Invidious users only have SID if !env.request.cookies.has_key? "SSID" - user = PG_DB.query_one?("SELECT * FROM users WHERE $1 = ANY(id)", sid, as: User) + email = PG_DB.query_one?("SELECT email FROM session_ids WHERE id = $1", sid, as: String) - if user + if email + user = PG_DB.query_one("SELECT * FROM users WHERE email = $1", email, as: User) challenge, token = create_response(user.email, "sign_out", HMAC_KEY, PG_DB, 1.week) env.set "challenge", challenge @@ -177,7 +178,7 @@ before_all do |env| end else begin - user = get_user(sid, headers, PG_DB, false) + user, sid = get_user(sid, headers, PG_DB, false) challenge, token = create_response(user.email, "sign_out", HMAC_KEY, PG_DB, 1.week) env.set "challenge", challenge @@ -312,7 +313,7 @@ get "/watch" do |env| end if watched && !watched.includes? id - PG_DB.exec("UPDATE users SET watched = watched || $1 WHERE $2 = id", [id], user.as(User).id) + PG_DB.exec("UPDATE users SET watched = watched || $1 WHERE email = $2", [id], user.as(User).email) end if nojs @@ -880,7 +881,7 @@ post "/login" do |env| sid = login.cookies["SID"].value - user = get_user(sid, headers, PG_DB) + user, sid = get_user(sid, headers, PG_DB) # We are now logged in @@ -986,7 +987,7 @@ post "/login" do |env| if Crypto::Bcrypt::Password.new(user.password.not_nil!) == password sid = Base64.urlsafe_encode(Random::Secure.random_bytes(32)) - PG_DB.exec("UPDATE users SET id = id || $1 WHERE LOWER(email) = LOWER($2)", [sid], email) + PG_DB.exec("INSERT INTO session_ids VALUES ($1, $2, $3)", sid, email, Time.now) if Kemal.config.ssl || CONFIG.https_only secure = true @@ -1024,13 +1025,14 @@ post "/login" do |env| end sid = Base64.urlsafe_encode(Random::Secure.random_bytes(32)) - user = create_user(sid, email, password) + user, sid = create_user(sid, email, password) user_array = user.to_a user_array[5] = user_array[5].to_json args = arg_array(user_array) PG_DB.exec("INSERT INTO users VALUES (#{args})", user_array) + PG_DB.exec("INSERT INTO session_ids VALUES ($1, $2, $3)", sid, email, Time.now) view_name = "subscriptions_#{sha256(user.email)[0..7]}" PG_DB.exec("CREATE MATERIALIZED VIEW #{view_name} AS \ @@ -1078,7 +1080,7 @@ get "/signout" do |env| user = env.get("user").as(User) sid = env.get("sid").as(String) - PG_DB.exec("UPDATE users SET id = array_remove(id, $1) WHERE email = $2", sid, user.email) + PG_DB.exec("DELETE FROM session_ids * WHERE id = $1", sid) env.request.cookies.each do |cookie| cookie.expires = Time.new(1990, 1, 1) @@ -1252,7 +1254,7 @@ get "/mark_watched" do |env| if user user = user.as(User) if !user.watched.includes? id - PG_DB.exec("UPDATE users SET watched = watched || $1 WHERE $2 = id", [id], user.id) + PG_DB.exec("UPDATE users SET watched = watched || $1 WHERE email = $2", [id], user.email) end end @@ -1347,9 +1349,10 @@ get "/subscription_manager" do |env| locale = LOCALES[env.get("locale").as(String)]? user = env.get? "user" + sid = env.get? "sid" referer = get_referer(env, "/") - if !user + if !user && !sid next env.redirect referer end @@ -1360,7 +1363,7 @@ get "/subscription_manager" do |env| headers = HTTP::Headers.new headers["Cookie"] = env.request.headers["Cookie"] - user = get_user(user.id[0], headers, PG_DB) + user, sid = get_user(sid, headers, PG_DB) end action_takeout = env.params.query["action_takeout"]?.try &.to_i? @@ -1756,10 +1759,12 @@ get "/feed/subscriptions" do |env| locale = LOCALES[env.get("locale").as(String)]? user = env.get? "user" + sid = env.get? "sid" referer = get_referer(env) if user user = user.as(User) + sid = sid.as(String) preferences = user.preferences if preferences.unseen_only @@ -1771,7 +1776,7 @@ get "/feed/subscriptions" do |env| headers["Cookie"] = env.request.headers["Cookie"] if !user.password - user = get_user(user.id[0], headers, PG_DB) + user, sid = get_user(sid, headers, PG_DB) end max_results = preferences.max_results diff --git a/src/invidious/users.cr b/src/invidious/users.cr index d45c5af4..67125654 100644 --- a/src/invidious/users.cr +++ b/src/invidious/users.cr @@ -12,7 +12,6 @@ class User end add_mapping({ - id: Array(String), updated: Time, notifications: Array(String), subscriptions: Array(String), @@ -126,18 +125,21 @@ class Preferences end def get_user(sid, headers, db, refresh = true) - if db.query_one?("SELECT EXISTS (SELECT true FROM users WHERE $1 = ANY(id))", sid, as: Bool) - user = db.query_one("SELECT * FROM users WHERE $1 = ANY(id)", sid, as: User) + if email = db.query_one?("SELECT email FROM session_ids WHERE id = $1", sid, as: String) + user = db.query_one("SELECT * FROM users WHERE email = $1", email, as: User) if refresh && Time.now - user.updated > 1.minute - user = fetch_user(sid, headers, db) + user, sid = fetch_user(sid, headers, db) user_array = user.to_a user_array[5] = user_array[5].to_json args = arg_array(user_array) db.exec("INSERT INTO users VALUES (#{args}) \ - ON CONFLICT (email) DO UPDATE SET id = users.id || $1, updated = $2, subscriptions = $4", user_array) + ON CONFLICT (email) DO UPDATE SET updated = $1, subscriptions = $3", user_array) + + db.exec("INSERT INTO session_ids VALUES ($1,$2,$3) \ + ON CONFLICT (id) DO NOTHING", sid, user.email, Time.now) begin view_name = "subscriptions_#{sha256(user.email)[0..7]}" @@ -149,14 +151,17 @@ def get_user(sid, headers, db, refresh = true) end end else - user = fetch_user(sid, headers, db) + user, sid = fetch_user(sid, headers, db) user_array = user.to_a user_array[5] = user_array[5].to_json args = arg_array(user.to_a) db.exec("INSERT INTO users VALUES (#{args}) \ - ON CONFLICT (email) DO UPDATE SET id = users.id || $1, updated = $2, subscriptions = $4", user_array) + ON CONFLICT (email) DO UPDATE SET updated = $1, subscriptions = $3", user_array) + + db.exec("INSERT INTO session_ids VALUES ($1,$2,$3) \ + ON CONFLICT (id) DO NOTHING", sid, user.email, Time.now) begin view_name = "subscriptions_#{sha256(user.email)[0..7]}" @@ -168,7 +173,7 @@ def get_user(sid, headers, db, refresh = true) end end - return user + return user, sid end def fetch_user(sid, headers, db) @@ -196,17 +201,17 @@ def fetch_user(sid, headers, db) token = Base64.urlsafe_encode(Random::Secure.random_bytes(32)) - user = User.new([sid], Time.now, [] of String, channels, email, DEFAULT_USER_PREFERENCES, nil, token, [] of String) - return user + user = User.new(Time.now, [] of String, channels, email, DEFAULT_USER_PREFERENCES, nil, token, [] of String) + return user, sid end def create_user(sid, email, password) password = Crypto::Bcrypt::Password.create(password, cost: 10) token = Base64.urlsafe_encode(Random::Secure.random_bytes(32)) - user = User.new([sid], Time.now, [] of String, [] of String, email, DEFAULT_USER_PREFERENCES, password.to_s, token, [] of String) + user = User.new(Time.now, [] of String, [] of String, email, DEFAULT_USER_PREFERENCES, password.to_s, token, [] of String) - return user + return user, sid end def create_response(user_id, operation, key, db, expire = 6.hours)