diff --git a/config/config.example.yml b/config/config.example.yml
index ae9509d2..0884079b 100644
--- a/config/config.example.yml
+++ b/config/config.example.yml
@@ -295,6 +295,32 @@ https_only: false
##
#admins: [""]
+##
+## Force Authentication Backend
+## If not provided falls back to the type query parameter
+##
+## Supported Values:
+## - invidious
+## - google
+## - oauth
+## - ldap (Not implemented !)
+## - saml (Not implemented !)
+##
+## Default: nil
+##
+
+##
+## OAuth Configuration
+##
+# oauth:
+# host: oauth.example.net
+# auth_uri: /oauth/authorize
+# token_uri: /oauth/token
+# info_uri: /oauth/userinfo
+# client_id: CLIENT_ID
+# client_secret: CLIENT_SEECRET
+# redirect_uri: https://invidious.eexample.net/login/oauth
+
# -----------------------------
# Background jobs
diff --git a/src/invidious.cr b/src/invidious.cr
index 4952b365..3f086dca 100644
--- a/src/invidious.cr
+++ b/src/invidious.cr
@@ -32,6 +32,9 @@ require "yaml"
require "compress/zip"
require "protodec/utils"
+require "oauth2"
+require "http/client"
+
require "./invidious/database/*"
require "./invidious/database/migrations/*"
require "./invidious/helpers/*"
diff --git a/src/invidious/config.cr b/src/invidious/config.cr
index a077c7fd..0cc43ca3 100644
--- a/src/invidious/config.cr
+++ b/src/invidious/config.cr
@@ -8,6 +8,18 @@ struct DBConfig
property dbname : String
end
+struct OAuthConfig
+ include YAML::Serializable
+
+ property host : String
+ property auth_uri : String
+ property token_uri : String
+ property info_uri : String
+ property client_id : String
+ property client_secret : String
+ property redirect_uri : String
+end
+
struct ConfigPreferences
include YAML::Serializable
@@ -123,6 +135,9 @@ class Config
# Use quic transport for youtube api
property use_quic : Bool = false
+ property auth_type : String? = nil
+ property oauth : OAuthConfig? = nil
+
# Saved cookies in "name1=value1; name2=value2..." format
@[YAML::Field(converter: Preferences::StringToCookies)]
property cookies : HTTP::Cookies = HTTP::Cookies.new
diff --git a/src/invidious/helpers/oauth.cr b/src/invidious/helpers/oauth.cr
new file mode 100644
index 00000000..b5236e74
--- /dev/null
+++ b/src/invidious/helpers/oauth.cr
@@ -0,0 +1,36 @@
+def oauth_get()
+ if oauth = CONFIG.oauth
+ oauth_host = oauth.host
+ oauth_auth_uri = oauth.auth_uri
+ oauth_token_uri = oauth.token_uri
+ oauth_info_uri = oauth.info_uri
+ oauth_client_id = oauth.client_id
+ oauth_client_secret = oauth.client_secret
+ oauth_redirect_uri = oauth.redirect_uri
+
+ OAuth2::Client.new(oauth_host, oauth_client_id, oauth_client_secret,
+ authorize_uri: oauth_auth_uri, token_uri: oauth_token_uri,
+ redirect_uri: oauth_redirect_uri)
+ else
+ raise Exception.new("Missing OAuth Config")
+ end
+end
+
+def oauth_auth(authorization_code)
+ oauth_get().get_access_token_using_authorization_code(authorization_code)
+end
+
+def oauth_info(token)
+ if oauth = CONFIG.oauth
+ oauth_host = oauth.host
+ oauth_info_uri = oauth.info_uri
+
+ client = HTTP::Client.new(oauth_host, tls: true)
+ token.authenticate(client)
+ response = client.get oauth_info_uri
+ client.close
+ response.body
+ else
+ raise Exception.new("Missing OAuth Config")
+ end
+end
diff --git a/src/invidious/routes/login.cr b/src/invidious/routes/login.cr
index 99fc13a2..60c56cbb 100644
--- a/src/invidious/routes/login.cr
+++ b/src/invidious/routes/login.cr
@@ -21,6 +21,10 @@ module Invidious::Routes::Login
account_type = env.params.query["type"]?
account_type ||= "invidious"
+ if CONFIG.auth_type
+ account_type = CONFIG.auth_type
+ end
+
captcha_type = env.params.query["captcha"]?
captcha_type ||= "image"
@@ -30,6 +34,70 @@ module Invidious::Routes::Login
templated "user/login"
end
+ def self.login_oauth(env)
+ locale = env.get("preferences").as(Preferences).locale
+
+ user = env.get? "user"
+ return env.redirect "/feed/subscriptions" if user
+
+ referer = get_referer(env, "/feed/subscriptions")
+
+ authorization_code = env.params.query["code"]?
+ if authorization_code
+ begin
+ token = oauth_auth(authorization_code)
+ info = JSON.parse(oauth_info(token))
+ email = info["email"].as_s
+ user = Invidious::Database::Users.select(email: email)
+ if user
+ sid = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
+ Invidious::Database::SessionIDs.insert(sid, email)
+ env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.domain, sid)
+
+ if env.request.cookies["PREFS"]?
+ cookie = env.request.cookies["PREFS"]
+ cookie.expires = Time.utc(1990, 1, 1)
+ env.response.cookies << cookie
+ end
+ else
+ if !CONFIG.registration_enabled
+ return error_template(400, "Registration has been disabled by administrator.")
+ end
+
+ sid = Base64.urlsafe_encode(Random::Secure.random_bytes(32))
+ user, sid = create_user(sid, email)
+
+ if language_header = env.request.headers["Accept-Language"]?
+ if language = ANG.language_negotiator.best(language_header, LOCALES.keys)
+ user.preferences.locale = language.header
+ end
+ end
+
+ Invidious::Database::Users.insert(user)
+ Invidious::Database::SessionIDs.insert(sid, email)
+ view_name = "subscriptions_#{sha256(user.email)}"
+ PG_DB.exec("CREATE MATERIALIZED VIEW #{view_name} AS #{MATERIALIZED_VIEW_SQL.call(user.email)}")
+
+ env.response.cookies["SID"] = Invidious::User::Cookies.sid(CONFIG.domain, sid)
+ if env.request.cookies["PREFS"]?
+ user.preferences = env.get("preferences").as(Preferences)
+ Invidious::Database::Users.update_preferences(user)
+
+ cookie = env.request.cookies["PREFS"]
+ cookie.expires = Time.utc(1990, 1, 1)
+ env.response.cookies << cookie
+ end
+ end
+
+ env.redirect referer
+ rescue
+ return error_template(403, "Invalid Authorization Code");
+ end
+ else
+ return error_template(403, "Missing Authorization Code");
+ end
+ end
+
def self.login(env)
locale = env.get("preferences").as(Preferences).locale
@@ -46,6 +114,10 @@ module Invidious::Routes::Login
account_type = env.params.query["type"]?
account_type ||= "invidious"
+ if CONFIG.auth_type
+ account_type = CONFIG.auth_type
+ end
+
case account_type
when "google"
tfa_code = env.params.body["tfa"]?.try &.lchop("G-")
@@ -308,6 +380,12 @@ module Invidious::Routes::Login
error_message = %(#{ex.message}
Traceback: