summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSamantaz Fox <coding@samantaz.fr>2022-02-04 04:46:38 +0100
committerSamantaz Fox <coding@samantaz.fr>2022-02-07 17:15:21 +0100
commit7ace3fc989d5b24104af92537dc3a67cf9f608c3 (patch)
treeaad4f908ca3a368e5ca9c9ff686d93a7fdfbe41b /src
parent170e75499816af460d76d05ce4d440d37a01e0fb (diff)
downloadinvidious-7ace3fc989d5b24104af92537dc3a67cf9f608c3.tar.gz
invidious-7ace3fc989d5b24104af92537dc3a67cf9f608c3.tar.bz2
invidious-7ace3fc989d5b24104af92537dc3a67cf9f608c3.zip
Move remaining user-related routes out of main file
Diffstat (limited to 'src')
-rw-r--r--src/invidious.cr333
-rw-r--r--src/invidious/routes/account.cr358
2 files changed, 372 insertions, 319 deletions
diff --git a/src/invidious.cr b/src/invidious.cr
index f4cae7ea..6f4f575b 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -364,16 +364,30 @@ end
Invidious::Routing.get "/results", Invidious::Routes::Search, :results
Invidious::Routing.get "/search", Invidious::Routes::Search, :search
+ # User login/out
Invidious::Routing.get "/login", Invidious::Routes::Login, :login_page
Invidious::Routing.post "/login", Invidious::Routes::Login, :login
Invidious::Routing.post "/signout", Invidious::Routes::Login, :signout
+ # User preferences
Invidious::Routing.get "/preferences", Invidious::Routes::PreferencesRoute, :show
Invidious::Routing.post "/preferences", Invidious::Routes::PreferencesRoute, :update
Invidious::Routing.get "/toggle_theme", Invidious::Routes::PreferencesRoute, :toggle_theme
Invidious::Routing.get "/data_control", Invidious::Routes::PreferencesRoute, :data_control
Invidious::Routing.post "/data_control", Invidious::Routes::PreferencesRoute, :update_data_control
+ # User account management
+ Invidious::Routing.get "/change_password", Invidious::Routes::Account, :get_change_password
+ Invidious::Routing.post "/change_password", Invidious::Routes::Account, :post_change_password
+ Invidious::Routing.get "/delete_account", Invidious::Routes::Account, :get_delete
+ Invidious::Routing.post "/delete_account", Invidious::Routes::Account, :post_delete
+ Invidious::Routing.get "/clear_watch_history", Invidious::Routes::Account, :get_clear_history
+ Invidious::Routing.post "/clear_watch_history", Invidious::Routes::Account, :post_clear_history
+ Invidious::Routing.get "/authorize_token", Invidious::Routes::Account, :get_authorize_token
+ Invidious::Routing.post "/authorize_token", Invidious::Routes::Account, :post_authorize_token
+ Invidious::Routing.get "/token_manager", Invidious::Routes::Account, :token_manager
+ Invidious::Routing.post "/token_ajax", Invidious::Routes::Account, :token_ajax
+
# Feeds
Invidious::Routing.get "/view_all_playlists", Invidious::Routes::Feeds, :view_all_playlists_redirect
Invidious::Routing.get "/feed/playlists", Invidious::Routes::Feeds, :playlists
@@ -412,325 +426,6 @@ define_v1_api_routes()
define_api_manifest_routes()
define_video_playback_routes()
-get "/change_password" do |env|
- locale = env.get("preferences").as(Preferences).locale
-
- user = env.get? "user"
- sid = env.get? "sid"
- referer = get_referer(env)
-
- if !user
- next env.redirect referer
- end
-
- user = user.as(User)
- sid = sid.as(String)
- csrf_token = generate_response(sid, {":change_password"}, HMAC_KEY)
-
- templated "change_password"
-end
-
-post "/change_password" do |env|
- locale = env.get("preferences").as(Preferences).locale
-
- user = env.get? "user"
- sid = env.get? "sid"
- referer = get_referer(env)
-
- if !user
- next env.redirect referer
- end
-
- user = user.as(User)
- sid = sid.as(String)
- token = env.params.body["csrf_token"]?
-
- # We don't store passwords for Google accounts
- if !user.password
- next error_template(400, "Cannot change password for Google accounts")
- end
-
- begin
- validate_request(token, sid, env.request, HMAC_KEY, locale)
- rescue ex
- next error_template(400, ex)
- end
-
- password = env.params.body["password"]?
- if !password
- next error_template(401, "Password is a required field")
- end
-
- new_passwords = env.params.body.select { |k, v| k.match(/^new_password\[\d+\]$/) }.map { |k, v| v }
-
- if new_passwords.size <= 1 || new_passwords.uniq.size != 1
- next error_template(400, "New passwords must match")
- end
-
- new_password = new_passwords.uniq[0]
- if new_password.empty?
- next error_template(401, "Password cannot be empty")
- end
-
- if new_password.bytesize > 55
- next error_template(400, "Password cannot be longer than 55 characters")
- end
-
- if !Crypto::Bcrypt::Password.new(user.password.not_nil!).verify(password.byte_slice(0, 55))
- next error_template(401, "Incorrect password")
- end
-
- new_password = Crypto::Bcrypt::Password.create(new_password, cost: 10)
- Invidious::Database::Users.update_password(user, new_password.to_s)
-
- env.redirect referer
-end
-
-get "/delete_account" do |env|
- locale = env.get("preferences").as(Preferences).locale
-
- user = env.get? "user"
- sid = env.get? "sid"
- referer = get_referer(env)
-
- if !user
- next env.redirect referer
- end
-
- user = user.as(User)
- sid = sid.as(String)
- csrf_token = generate_response(sid, {":delete_account"}, HMAC_KEY)
-
- templated "delete_account"
-end
-
-post "/delete_account" do |env|
- locale = env.get("preferences").as(Preferences).locale
-
- user = env.get? "user"
- sid = env.get? "sid"
- referer = get_referer(env)
-
- if !user
- next env.redirect referer
- end
-
- user = user.as(User)
- sid = sid.as(String)
- token = env.params.body["csrf_token"]?
-
- begin
- validate_request(token, sid, env.request, HMAC_KEY, locale)
- rescue ex
- next error_template(400, ex)
- end
-
- view_name = "subscriptions_#{sha256(user.email)}"
- Invidious::Database::Users.delete(user)
- Invidious::Database::SessionIDs.delete(email: user.email)
- PG_DB.exec("DROP MATERIALIZED VIEW #{view_name}")
-
- env.request.cookies.each do |cookie|
- cookie.expires = Time.utc(1990, 1, 1)
- env.response.cookies << cookie
- end
-
- env.redirect referer
-end
-
-get "/clear_watch_history" do |env|
- locale = env.get("preferences").as(Preferences).locale
-
- user = env.get? "user"
- sid = env.get? "sid"
- referer = get_referer(env)
-
- if !user
- next env.redirect referer
- end
-
- user = user.as(User)
- sid = sid.as(String)
- csrf_token = generate_response(sid, {":clear_watch_history"}, HMAC_KEY)
-
- templated "clear_watch_history"
-end
-
-post "/clear_watch_history" do |env|
- locale = env.get("preferences").as(Preferences).locale
-
- user = env.get? "user"
- sid = env.get? "sid"
- referer = get_referer(env)
-
- if !user
- next env.redirect referer
- end
-
- user = user.as(User)
- sid = sid.as(String)
- token = env.params.body["csrf_token"]?
-
- begin
- validate_request(token, sid, env.request, HMAC_KEY, locale)
- rescue ex
- next error_template(400, ex)
- end
-
- Invidious::Database::Users.clear_watch_history(user)
- env.redirect referer
-end
-
-get "/authorize_token" do |env|
- locale = env.get("preferences").as(Preferences).locale
-
- user = env.get? "user"
- sid = env.get? "sid"
- referer = get_referer(env)
-
- if !user
- next env.redirect referer
- end
-
- user = user.as(User)
- sid = sid.as(String)
- csrf_token = generate_response(sid, {":authorize_token"}, HMAC_KEY)
-
- scopes = env.params.query["scopes"]?.try &.split(",")
- scopes ||= [] of String
-
- callback_url = env.params.query["callback_url"]?
- if callback_url
- callback_url = URI.parse(callback_url)
- end
-
- expire = env.params.query["expire"]?.try &.to_i?
-
- templated "authorize_token"
-end
-
-post "/authorize_token" do |env|
- locale = env.get("preferences").as(Preferences).locale
-
- user = env.get? "user"
- sid = env.get? "sid"
- referer = get_referer(env)
-
- if !user
- next env.redirect referer
- end
-
- user = env.get("user").as(User)
- sid = sid.as(String)
- token = env.params.body["csrf_token"]?
-
- begin
- validate_request(token, sid, env.request, HMAC_KEY, locale)
- rescue ex
- next error_template(400, ex)
- end
-
- scopes = env.params.body.select { |k, v| k.match(/^scopes\[\d+\]$/) }.map { |k, v| v }
- callback_url = env.params.body["callbackUrl"]?
- expire = env.params.body["expire"]?.try &.to_i?
-
- access_token = generate_token(user.email, scopes, expire, HMAC_KEY)
-
- if callback_url
- access_token = URI.encode_www_form(access_token)
- url = URI.parse(callback_url)
-
- if url.query
- query = HTTP::Params.parse(url.query.not_nil!)
- else
- query = HTTP::Params.new
- end
-
- query["token"] = access_token
- url.query = query.to_s
-
- env.redirect url.to_s
- else
- csrf_token = ""
- env.set "access_token", access_token
- templated "authorize_token"
- end
-end
-
-get "/token_manager" do |env|
- locale = env.get("preferences").as(Preferences).locale
-
- user = env.get? "user"
- sid = env.get? "sid"
- referer = get_referer(env, "/subscription_manager")
-
- if !user
- next env.redirect referer
- end
-
- user = user.as(User)
- tokens = Invidious::Database::SessionIDs.select_all(user.email)
-
- templated "token_manager"
-end
-
-post "/token_ajax" do |env|
- locale = env.get("preferences").as(Preferences).locale
-
- user = env.get? "user"
- sid = env.get? "sid"
- referer = get_referer(env)
-
- redirect = env.params.query["redirect"]?
- redirect ||= "true"
- redirect = redirect == "true"
-
- if !user
- if redirect
- next env.redirect referer
- else
- next error_json(403, "No such user")
- end
- end
-
- user = user.as(User)
- sid = sid.as(String)
- token = env.params.body["csrf_token"]?
-
- begin
- validate_request(token, sid, env.request, HMAC_KEY, locale)
- rescue ex
- if redirect
- next error_template(400, ex)
- else
- next error_json(400, ex)
- end
- end
-
- if env.params.query["action_revoke_token"]?
- action = "action_revoke_token"
- else
- next env.redirect referer
- end
-
- session = env.params.query["session"]?
- session ||= ""
-
- case action
- when .starts_with? "action_revoke_token"
- Invidious::Database::SessionIDs.delete(sid: session, email: user.email)
- else
- next error_json(400, "Unsupported action #{action}")
- end
-
- if redirect
- env.redirect referer
- else
- env.response.content_type = "application/json"
- "{}"
- end
-end
-
# Channels
{"/channel/:ucid/live", "/user/:user/live", "/c/:user/live"}.each do |route|
diff --git a/src/invidious/routes/account.cr b/src/invidious/routes/account.cr
new file mode 100644
index 00000000..2be0de37
--- /dev/null
+++ b/src/invidious/routes/account.cr
@@ -0,0 +1,358 @@
+{% skip_file if flag?(:api_only) %}
+
+module Invidious::Routes::Account
+ extend self
+
+ # -------------------
+ # Password update
+ # -------------------
+
+ # Show the password change interface (GET request)
+ def get_change_password(env)
+ locale = env.get("preferences").as(Preferences).locale
+
+ user = env.get? "user"
+ sid = env.get? "sid"
+ referer = get_referer(env)
+
+ if !user
+ return env.redirect referer
+ end
+
+ user = user.as(User)
+ sid = sid.as(String)
+ csrf_token = generate_response(sid, {":change_password"}, HMAC_KEY)
+
+ templated "change_password"
+ end
+
+ # Handle the password change (POST request)
+ def post_change_password(env)
+ locale = env.get("preferences").as(Preferences).locale
+
+ user = env.get? "user"
+ sid = env.get? "sid"
+ referer = get_referer(env)
+
+ if !user
+ return env.redirect referer
+ end
+
+ user = user.as(User)
+ sid = sid.as(String)
+ token = env.params.body["csrf_token"]?
+
+ # We don't store passwords for Google accounts
+ if !user.password
+ return error_template(400, "Cannot change password for Google accounts")
+ end
+
+ begin
+ validate_request(token, sid, env.request, HMAC_KEY, locale)
+ rescue ex
+ return error_template(400, ex)
+ end
+
+ password = env.params.body["password"]?
+ if !password
+ return error_template(401, "Password is a required field")
+ end
+
+ new_passwords = env.params.body.select { |k, v| k.match(/^new_password\[\d+\]$/) }.map { |k, v| v }
+
+ if new_passwords.size <= 1 || new_passwords.uniq.size != 1
+ return error_template(400, "New passwords must match")
+ end
+
+ new_password = new_passwords.uniq[0]
+ if new_password.empty?
+ return error_template(401, "Password cannot be empty")
+ end
+
+ if new_password.bytesize > 55
+ return error_template(400, "Password cannot be longer than 55 characters")
+ end
+
+ if !Crypto::Bcrypt::Password.new(user.password.not_nil!).verify(password.byte_slice(0, 55))
+ return error_template(401, "Incorrect password")
+ end
+
+ new_password = Crypto::Bcrypt::Password.create(new_password, cost: 10)
+ Invidious::Database::Users.update_password(user, new_password.to_s)
+
+ env.redirect referer
+ end
+
+ # -------------------
+ # Account deletion
+ # -------------------
+
+ # Show the account deletion confirmation prompt (GET request)
+ def get_delete(env)
+ locale = env.get("preferences").as(Preferences).locale
+
+ user = env.get? "user"
+ sid = env.get? "sid"
+ referer = get_referer(env)
+
+ if !user
+ return env.redirect referer
+ end
+
+ user = user.as(User)
+ sid = sid.as(String)
+ csrf_token = generate_response(sid, {":delete_account"}, HMAC_KEY)
+
+ templated "delete_account"
+ end
+
+ # Handle the account deletion (POST request)
+ def post_delete(env)
+ locale = env.get("preferences").as(Preferences).locale
+
+ user = env.get? "user"
+ sid = env.get? "sid"
+ referer = get_referer(env)
+
+ if !user
+ return env.redirect referer
+ end
+
+ user = user.as(User)
+ sid = sid.as(String)
+ token = env.params.body["csrf_token"]?
+
+ begin
+ validate_request(token, sid, env.request, HMAC_KEY, locale)
+ rescue ex
+ return error_template(400, ex)
+ end
+
+ view_name = "subscriptions_#{sha256(user.email)}"
+ Invidious::Database::Users.delete(user)
+ Invidious::Database::SessionIDs.delete(email: user.email)
+ PG_DB.exec("DROP MATERIALIZED VIEW #{view_name}")
+
+ env.request.cookies.each do |cookie|
+ cookie.expires = Time.utc(1990, 1, 1)
+ env.response.cookies << cookie
+ end
+
+ env.redirect referer
+ end
+
+ # -------------------
+ # Clear history
+ # -------------------
+
+ # Show the watch history deletion confirmation prompt (GET request)
+ def get_clear_history(env)
+ locale = env.get("preferences").as(Preferences).locale
+
+ user = env.get? "user"
+ sid = env.get? "sid"
+ referer = get_referer(env)
+
+ if !user
+ return env.redirect referer
+ end
+
+ user = user.as(User)
+ sid = sid.as(String)
+ csrf_token = generate_response(sid, {":clear_watch_history"}, HMAC_KEY)
+
+ templated "clear_watch_history"
+ end
+
+ # Handle the watch history clearing (POST request)
+ def post_clear_history(env)
+ locale = env.get("preferences").as(Preferences).locale
+
+ user = env.get? "user"
+ sid = env.get? "sid"
+ referer = get_referer(env)
+
+ if !user
+ return env.redirect referer
+ end
+
+ user = user.as(User)
+ sid = sid.as(String)
+ token = env.params.body["csrf_token"]?
+
+ begin
+ validate_request(token, sid, env.request, HMAC_KEY, locale)
+ rescue ex
+ return error_template(400, ex)
+ end
+
+ Invidious::Database::Users.clear_watch_history(user)
+ env.redirect referer
+ end
+
+ # -------------------
+ # Authorize tokens
+ # -------------------
+
+ # Show the "authorize token?" confirmation prompt (GET request)
+ def get_authorize_token(env)
+ locale = env.get("preferences").as(Preferences).locale
+
+ user = env.get? "user"
+ sid = env.get? "sid"
+ referer = get_referer(env)
+
+ if !user
+ return env.redirect referer
+ end
+
+ user = user.as(User)
+ sid = sid.as(String)
+ csrf_token = generate_response(sid, {":authorize_token"}, HMAC_KEY)
+
+ scopes = env.params.query["scopes"]?.try &.split(",")
+ scopes ||= [] of String
+
+ callback_url = env.params.query["callback_url"]?
+ if callback_url
+ callback_url = URI.parse(callback_url)
+ end
+
+ expire = env.params.query["expire"]?.try &.to_i?
+
+ templated "authorize_token"
+ end
+
+ # Handle token authorization (POST request)
+ def post_authorize_token(env)
+ locale = env.get("preferences").as(Preferences).locale
+
+ user = env.get? "user"
+ sid = env.get? "sid"
+ referer = get_referer(env)
+
+ if !user
+ return env.redirect referer
+ end
+
+ user = env.get("user").as(User)
+ sid = sid.as(String)
+ token = env.params.body["csrf_token"]?
+
+ begin
+ validate_request(token, sid, env.request, HMAC_KEY, locale)
+ rescue ex
+ return error_template(400, ex)
+ end
+
+ scopes = env.params.body.select { |k, v| k.match(/^scopes\[\d+\]$/) }.map { |k, v| v }
+ callback_url = env.params.body["callbackUrl"]?
+ expire = env.params.body["expire"]?.try &.to_i?
+
+ access_token = generate_token(user.email, scopes, expire, HMAC_KEY)
+
+ if callback_url
+ access_token = URI.encode_www_form(access_token)
+ url = URI.parse(callback_url)
+
+ if url.query
+ query = HTTP::Params.parse(url.query.not_nil!)
+ else
+ query = HTTP::Params.new
+ end
+
+ query["token"] = access_token
+ url.query = query.to_s
+
+ env.redirect url.to_s
+ else
+ csrf_token = ""
+ env.set "access_token", access_token
+ templated "authorize_token"
+ end
+ end
+
+ # -------------------
+ # Manage tokens
+ # -------------------
+
+ # Show the token manager page (GET request)
+ def token_manager(env)
+ locale = env.get("preferences").as(Preferences).locale
+
+ user = env.get? "user"
+ sid = env.get? "sid"
+ referer = get_referer(env, "/subscription_manager")
+
+ if !user
+ return env.redirect referer
+ end
+
+ user = user.as(User)
+ tokens = Invidious::Database::SessionIDs.select_all(user.email)
+
+ templated "token_manager"
+ end
+
+ # -------------------
+ # AJAX for tokens
+ # -------------------
+
+ # Handle internal (non-API) token actions (POST request)
+ def token_ajax(env)
+ locale = env.get("preferences").as(Preferences).locale
+
+ user = env.get? "user"
+ sid = env.get? "sid"
+ referer = get_referer(env)
+
+ redirect = env.params.query["redirect"]?
+ redirect ||= "true"
+ redirect = redirect == "true"
+
+ if !user
+ if redirect
+ return env.redirect referer
+ else
+ return error_json(403, "No such user")
+ end
+ end
+
+ user = user.as(User)
+ sid = sid.as(String)
+ token = env.params.body["csrf_token"]?
+
+ begin
+ validate_request(token, sid, env.request, HMAC_KEY, locale)
+ rescue ex
+ if redirect
+ return error_template(400, ex)
+ else
+ return error_json(400, ex)
+ end
+ end
+
+ if env.params.query["action_revoke_token"]?
+ action = "action_revoke_token"
+ else
+ return env.redirect referer
+ end
+
+ session = env.params.query["session"]?
+ session ||= ""
+
+ case action
+ when .starts_with? "action_revoke_token"
+ Invidious::Database::SessionIDs.delete(sid: session, email: user.email)
+ else
+ return error_json(400, "Unsupported action #{action}")
+ end
+
+ if redirect
+ return env.redirect referer
+ else
+ env.response.content_type = "application/json"
+ return "{}"
+ end
+ end
+end