Flask GET ve POST İşlemleri (Twitch İçerir)

Merhaba. Öncelikle ne yaptığımı anlatayım daha sonrasında sorumu size yönelticem ki beni daha iyi anlayın.

Önceki açtığım Şu konudan sonra orada yaptığımı bırakıp kenara çekildim ve kendi kendime dedim ki, twitchte olan moderasyon botlarından bende kendime ait olan bir tanesini yapmalıyım dedim. Başladım işe koyulmaya. Twitchin dökümanından verilebilen izinlere, izinler doğrultusunda neler yaptırabileceğime, Kullanıcının bağırarak (CAPS LOCK açık bir şekilde) mesaj yazdığını tespit ettiğinde chatten silmesine(yayıncı ve moderatörler hariç), kullanıcıların kullanabileceği özel komutlar, yalnızca moderatörlerin ve yayıncının kullanabildiği bazı komutlar ekledim, Yayının başlığını değiştirebilme özelliği ekledim. Kısacası MooBot’un yapabildiklerini aşağı yukarı bende yapmaya çalıştım. Adını verdiğim botu diğer kullanıcılara da sorduğumda “Bir internet sitesi var ordan oradan yapıyorsun” dediklerini gördüm. Demeleri üzerine botu inceledim ve böyle bir şeyi bende yapabilirim diye düşündüm. Hiç tecrübemin olmadığı flask framework’ünü kurdum. Videolar ve yazılar okuyarak ordan burdan bakarak biraz yol kat ettim. Database kullanımına baktım normal sqlite3’ü denedim sonrasında baktığımda sqlalchemy diye bir method olduğunu gördüm. Fazla karmaşık ve sanki biraz gereksiz bir kullanım gibi geldi. sonrasını düşünerek hareket etmeye çalıştığımdan firebase adında google’ın da işin içinde bulunduğu(önceden bildiğim ama kullanamadığım) bir veri tabanını gördüm. Veri tabanı demek yanlış olur keza çok kapsamlı o yüzden ne diyeceğimi bilmiyorum :nerd_face: Onu kullanarak içerisine yayıncının access tokenini kayıt ettim sonrası ordan alıp okuma tabiiki oralar daha sonra halledilir :upside_down_face:

Bir ana sayfa yaptım (sadece “Ana sayfa” ve “Twitch ile giriş yap” butonu var). Gördüm ki yönlendirme(redirect) denilen bir durum varmış (botun yalnızca ilgili kanalda kullanılabilmesi için örneğin başlığı değiştir denince ilgili kanala ait yayının başlığını değiştirmek gibi…) Bu yönlendirme linki de şu şekilde: (https://id.twitch.tv/oauth2/authorize?client_id=rhxkti3axfpp3cuc50g03hp1j3jroq&redirect_uri=http://localhost:5000&response_type=code&scope=chat:read+chat:edit+channel:moderate+channel:manage:broadcast+channel:read:editors+channel:read:subscriptions+user:read:email) Buradaki yönlendirme sonucu ilgili twitch adresimizi girmek üzere bir pencereye yönlendiriyor. Girildikten sonra “Yetkilendir” butonuna basılıyor. Basıldıktan sonra verdiğim yönlendirme linkini incelerseniz redirect_uri parametresine karşılık gelen argumanda benim ana sayfama yönlendiriyor. Yönlendirme işleminin hemen ardından ana sayfamın bulunduğu urlde code adında bir parametre oluşuyor. Bu parametredeki değerle post işlemi yaparak bizim BEARER kodumuz yani access token'ımızi vermiş oluyor.

Buraya kadar yaptıklarımı kod olarak atıyorum.

Flask ile ilgili app.py
from pprint import pprint
from flask import Flask,render_template,request,redirect,url_for
from util.twitch_ import TwitchAPI
from util.twitch_ import FireBase_DB

imgProfile = None
app = Flask(__name__)

@app.route("/")
def homePage(): # app.py ilk çalıştığında homePage adında yanı bu fonksiyon çalışıyor.
    global imgProfile
    code = request.args.get("code")
    if code: # fonksiyon çalıştıktan sonra eğer url de "code" adında bir parametreye sahip arguman varsa
        twitch = TwitchAPI(code) # kendi oluşturduğum sınıfa arguman olarak veriyorum bunun kod kısmınıda paylaşıcam
        token = twitch.getToken() # ilgili BEARER kodumu (access token) alıyorum
        user = twitch.getUser(token) # Yayıncı hakkında bilgi aldığım bir method arguman olarak tokeni gönderiyorum
        userN = user["data"][0]["display_name"] # Yayıncının adını barındıran json formatından ayıklanan yayıncı adı
        imgProfile = user["data"][0]["profile_image_url"] # Yayıncının profil resmini barındıran  json formatından ayıklanan yayıncı profil resmi
        db.write(token) # FireBase_DB adını verdiğim sınıfımdaki methodu kullanıyorum. arguman olarak token değişkenini veriyorum. Bu FireBase(google)'de RealtimeDatabase'e yazıyor.
        return redirect(url_for("login", streamer=userN)) # redirect ile içerisine girilen url'ye yönlendiriliyor. url_for fonksiyonu ile kendi "login" adını verdiğim fonksiyona yönlendiriyorum ikinci aldığı parametre yayıncının adı oluyor
    else:  # fonksiyon çalıştıktan sonra eğer url de "code" adında bir parametreye sahip arguman yoksa
        return render_template("index.html") # ana sayfanın görüntüsü

@app.route("/login/r/<streamer>") # Dinamik bir url oluşturuyorum fonksiyona girdiğim parametre ile buraya yazdığım dinamik değerin adı aynı olmalı üstteki url_for da da keza öyle
def login(streamer):
    return render_template("login.html", streamer=streamer,img=imgProfile) # html sayfasında görebilmek için parametre olarak veriyorum vermesemde olur bu o kadar muhim değil

if __name__ == "__main__":
    db = FireBase_DB() # kendi oluşturduğum sınıfı çağırıyorum
    app.run(debug=True) # Kaydettikten sonra otomatik yeniden başlatması için debug parametresini True olarak ayarlıyorum

Şimdi kod açıklamalarında sözünü ettiğim, kendim oluşturduğum sınıfların kodunu veriyorum:

util klasörü Firebase ve Twitch ile ilgili twitch_.py dosyası
import requests
from firebase_admin import credentials
from firebase_admin import db
import firebase_admin # google'ın oluşturduğu FireBase ile ilgili modül

class TwitchAPI:
    def __init__(self,code:str) -> None:
        "Yönlendirilen URL'deki code parametresine karşılık gelen argumanı gir"
        self.code = code

    def getUser(self,access:str) -> dict: # https://api.twitch.tv/helix/users
        "Yayıncı Hakkında bilgi verir sözlük tipinde değer döndürür"

        __headers = {
            "Authorization" : f"Bearer {access}",
            "Client-Id" : "rhxkti3axfpp3cuc50g03hp1j3jroq" # ilgili botun twitch tarafından verilen client id'si
        }
        self.resp = requests.get("https://api.twitch.tv/helix/users",headers=__headers).json()
        return self.resp
    
    def getToken(self) -> dict: # https://id.twitch.tv/oauth2/token"
        "Twitch için gerekli `access_token` anahtarını alır"

        __params = {
            "client_id": "rhxkti3axfpp3cuc50g03hp1j3jroq", # ilgili botun twitch tarafından verilen client id'si
            "client_secret": "<client_secret adresim(bot olarak kullandığım hesabın)>",
            "code" : self.code,
            "grant_type" : "authorization_code",
            "redirect_uri" : "http://localhost:5000"
        }
        response = requests.post("https://id.twitch.tv/oauth2/token",params=__params).json()
        return response["access_token"]

class FireBase_DB(TwitchAPI):
    def __init__(self) -> None:
        if not firebase_admin._apps:
            __cred = credentials.Certificate("flaskprojesi-sdk.json")
            firebase_admin.initialize_app(__cred,{"databaseURL":"<databasenin bulunduğu url>"})
            self.ref = db.reference("Streamers") # "Streamers" adında bir başlık(doğru telaffuz mu bilmiyorum) oluşturdum
    
    def write(self,bearer:str) -> None:
        "Yayıncıya ait `Bearer` kodunu Veri tabanına yazar"
        json_data = super().getUser(bearer)
        self.ref.update({json_data["data"][0]["display_name"] : bearer}) # yayıncı "key" olmak üzere "value" karşılığı Bearer kodunu yazdırıyorum
    
    def read(self,user:str): # Burayı henüz düzenlemedim ekledim sonrasında ayarlayacağım
        "Kullanıcı adına ait bearer kodu döndürür"
        return self.ref.get()[user]

Takıldığım nokta şu; Yönlendirme işleminde code değerini alıp ardından token değerine sahip oluyorum evet bu çok iyi. Aldıktan sonra kullanıcı kendi sayfasına yönlendirme yöntemini GET methodu ile yapıyor. Yani bu durum şu oluyor: https://localhost:5000/login/r/arif yazan herhangi bir kişi benim sayfama girmiş oluyor. bunu POST methodu ile yaptığımda da bana yapılamacağına dair bir uyarı veriyor. Ben bu duruma bir önlem alamıyorum. Şimdi bunun bir ss’ini atıyorum:

Unutmadan söyleyeyim en başta twitch yönlendirme urlsini html dosyasında a etiketinde kullanıyorum
Hemen onun da kodunu buraya yazıyorum:

Ana sayfanın bulunduğu html sayfası index.html

Html konusunda neredeyse hiç bilgi ve tecrübe sahibi değilim sürekli deneyerek geldiğim durum şuanki gördüğünüz gibi

<!DOCTYPE html>
<html lang="tr">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="/static/css/bootstrap.min.css">
    <title>Ana Sayfa</title>
</head>
<body>
    <h1>Ana Sayfa</h1>
    <a href="https://id.twitch.tv/oauth2/authorize?client_id=rhxkti3axfpp3cuc50g03hp1j3jroq&redirect_uri=http://localhost:5000&response_type=code&scope=chat:read+chat:edit+channel:moderate+channel:manage:broadcast+channel:read:editors+channel:read:subscriptions+user:read:email" class="btn btn-warning">Connect with Twitch</a>
<script src="/static/js/bootstrap.bundle.min.js"></script>
</body>
</html>

image

Takıldığım diğer bir nokta ise şu; Atıyorum kullanıcı bir kez giriş yaptı. Siteye tekrar girmeye kalktığında kullanıcı sayfasına nasıl yönlendirebilirim ? Sonuçta bilgisayarda “.ini” dosyası gibi bir dosya kaydettirip ordan durum sorgulatma yapamam her nihayetinde bu bir internet sitesi (en azından öyle :nerd_face: )

Çok uzun oldu farkındayım fakat başka nasıl açıklayabilirdim bilmiyorum. Yardımlarınız için şimdiden teşekkür ediyorum.

3 Beğeni

anladığımdan emin değilim ama kullanıcının access tokenini cooike olarak saklamayı denediniz mi

Bu zaten veri tabanında kayıtlı hocam.

Giriş yapan kullanıcının adı ve adına karşılık gelen access token veri tabanına ekleniyor.
Bu konuda bir sorunum yok

eğer cookie olarak kayıt edebilirseniz ana sayfaya gelen istekte cookie kontrolü yaparak kullanıcıyı sayfaya yönlendirebilirsiniz

Tamam onun için bakınıcam internetten. Peki açtığım konuda ilk takıldığım sorun hakkında ne düşünüyorsunuz. ?

eğer herhangi bir kişinin değil yalnızca sahibinin girmesi gerekiyorsa cookie ile bunu yapabilirsiniz. Post methodu ile istek geldiğinde de uyarıyı kaldırıp erişim verilmesini istiyorsanız

@app.route("/login/r/<streamer>", methods=["GET", "POST"])

satırınızı şu şekilde düzenleyerek gelen GET ve POST methodlarına izin verebilirsiniz

Şimdi dekoratörde gösterdiğiniz gibi öncesinde yapmıştım. Post işlemi yapmıyor fakat get olduğunda kullanıcı sayfasına gidiyor. Get olduğunda da ilgili kişinin sayfasına bu sefer sizde girebilmiş oluyorsunuz. En azından iki farklı tarayıcıda test ettiğimde yaşadım.

Çerezler kısmını incelemem gerekiyor nasıl yapılıyor ediliyor diye.

Evet sevgili @WarForPeace 'in söylediği çerezler ile ilgili fikri için teşekkür ediyorum. Söylediği şekilde internetten baktığımda flask ile bunun yapılabildiğini gördüm. app.pydaki login fonksiyonunu düzenledim ve yeni bir fonksiyon oluşturdum. homePage fonksiyonundaki yönlendirmeyi de direk yeni oluşturduğum fonksiyon olarak atadım.

@app.route("/loading")
def createCookie(): # cookie oluşturma
    resp = make_response(redirect(url_for("login", streamer=userN)))
    resp.set_cookie("streamer",userN)
    return resp

@app.route("/login/r/<streamer>")
def login(streamer):
    if not request.cookies.get("streamer") == streamer:
        return redirect(url_for("homePage"))
    else:
        return render_template("login.html", streamer=streamer,img=imgProfile)

kullanıcı adı varsa kullanıcı sayfasını açıyor, yoksa ana sayfaya gidiyor. Bu şekilde dekoratörde get ya da post olarak belirtmeme gerek kalmamış oluyor.
Teşekkür ediyorum @WarForPeace öneriniz için.

Yararlandığım kaynak:

Geriye yalnızca biraz mantıksal bir hareket gerekiyor kullanıcı sayfasından başlamak için onuda sonrasında düşüneceğim.

Peki Bunun daha güvenli olabilmesi adına sizce ne gibi önlemler alınabilir ?
Sonuçta çerezlerde bir tek kullanıcının adını barındırıyor. Ve buda açık bir şekilde.

açıkçası şu an pek bir şey gelmedi aklıma jwt kullanabilirsiniz

Session cookie.

Kullanicinin adi yerine access token tutulabilir.

Hocam aslında


access tokeni firebase’de tutuyorum. (iki ayrı hesapla deneme yaptığımdan iki ayrı hesabın olduğunu görüyorsunuz).

Ama yine de session cookie aracılığı ile bunun yalnızca ilgili kullanıcıya ait olanını tutmalıyım ki, bu cookie sahip olmayan kullanıcılar kişinin hesabına giremesin ?

peki hocam session cookie ile normal olarak adlandırılan cookie arasındaki fark nedir ?

Session cookie’ye data olarak session ID yaziyosun. Ilgili data arkada veritabaninda o ID karsisinda duruyor, ve belirli bir sure.

Cookie’ye access token koy dedigim de buydu. (Dogrusu rastgele ID uretmek ama kolay olsun diye zaten var olan access token kullan dedim)

user=arif_cem’i user=helmsys__'e cevirebilir ama tok=arif_cem:jjtlhjgh’yi hic bir seye ceviremez.

1 Beğeni

Hocam merhaba. Session Cookie’yi internetten bakındım. Anladığım kadarı ile browserimizden çıkana kadar geçerli olan cookie türüymüş session cookieler.
Bu haliyle kullanırsam mantık olarak http://localhost:5000/login/r/arif_cem adresine GET işlemi uygulandığı vakit buna sizde girebilirsiniz bir başkası da. Bunu önlemek için normal olarak adlandırılan cookie’ye başvurdum. ki Yalnızca ilgili kişide belirlenen cookie olduğunda get işlemi yapabilsin.

Şu söyleminizi yanlış uyarlamış olabilirim.
browserda bu şekilde görünüyor:
image
value deki değeri jwt.io’ya aynen yapıştırdığında kabak gibi gözüküyor. Ya tabiikide ne yapılırsa yapılsın birşekilde çözülebilir fakat, sizce nasıl bir yol izlenmeli ?
Şöyle kodumu atıyorum Sizce nasıl bir hareket gerekli ?

SS’te de görüldüğü gibi değeri şifrelemek için PyJWT modülünden yararlandım.

@app.route("/loading")
def createCookie(): # cookie oluşturma
    resp = make_response(redirect(url_for("login", streamer=userN)))
    resp.set_cookie("token",encryptToken())
    return resp

@app.route("/login/r/<streamer>")
def login(streamer):
    if not request.cookies.get("token") == encryptToken():
        return redirect(url_for("homePage"))
    else:
        # print(request.cookies.get("streamer"))
        return render_template("login.html", streamer=streamer,img=imgProfile)

def encryptToken():
   return jwt.encode({userN:db.read(userN)},"passw") # "tok=arif_cem:jjtlhjgh"gösterdiğiniz metotda ilerledim fakat yanlış anlamışş olabilirim 

hocam unutmadan söyleyeyim db.read, bize kullanıcının berarer tokenini veritabanından alıp bu ifadeyi döndürüyor

jwt için kullandığınız secret keyi bilmeleri gerekiyor (içindeki veriyi elbet görebilirler bilmeseler de)" /login/r/arif_cem" adresine istek geldiğinde cookieden veriyi decode etmeye çalıştığınızda secret key ile eğer doğru key ise sorun yaratmayacaktır fakat başka biri çakallık yapıp cookieyi düzenleyerek gönderirse secret key farklı olduğu için hata verecektir

bunu algoritma olarak düzenlemeli miyim ?

Çünkü secret key’i hiçbir şekilde girmeme rağmen https://token.dev/ gibi sitelerde decode ettiğimde kabak gibi çıkarıyor. Yani secret key gibi bir değer girme söz konusu değil. sadece encode olarak döndürülen bu da cookienin değeri olan veriyi sorgulatıyorum.

evet elbette ama kullanıcının cookiesine başka birinin erişemeyeceğini düşünüyorum

Welcome to PyJWT — PyJWT 2.4.0 documentation örneğinde de belirttiği gibi

encoded_jwt = jwt.encode({"some": "payload"}, "secret", algorithm="HS256")
encode işleminde bir secret key giriyorsunuz sizin girdiğiniz key “passw”

siz login fonksiyonunda direkt login.html dosyasını render etmek yerine

jwt.decode(request.cookies.get("token"), "passw", algorithms=["HS256"])

şeklinde decode ederek eğer doğru secret key ile encode edilmiş ise hata vermeden datayı döndürecek eğer farklı bir secret key ile encode edilmiş ise invalid signature hatası verecektir dolayısıyla hatalı token geldiğinde sayfaya girmesini engelleyebilirsiniz

1 Beğeni

eğer erişebileceğinden şüpheniz var ise kullanıcının ismini de encode edebilirsiniz yalnızca token kullanmadan

Hocam aşağı yukarı aynı mantıkta işliyor. Eğer benim tokenimin yazılı olduğu cookie sizde yoksa siz benim hesabıma giremiyorsunuz.

Sizin söylediğinzi şekilde yaptığımda yani;

@app.route("/login/r/<streamer>")
def login(streamer):
    try:
        if jwt.decode(request.cookies.get("token"),"passw",algorithms="HS256"):
            return render_template("login.html", streamer=streamer,img=imgProfile)
    except except_.DecodeError:
         return redirect(url_for("homePage"))

Şimdi şifrelenmek istenen değer şu “kullanıcı_adı : access_token” bunu yapıyoruz. Sonra encode ediyoruz. Encode edilen bu değer doğru decode edilirse giriş sayfasına yönlendiriyor, yalışsa anasayfaya. İki farklı tarayıcı ile yaptığımda çalıştığına şahit oldum.
Aslında fazla kurcalamaya gerek yokmuş. Sadece requests modülü kullanarak bazı sitelere giriş yapılıyor ya, aklıma böyle düşünceler geldiğinden işin suyunu çıkarmış oldum ama bakıldığı zaman gerçektende gerek yokmuş…
Sizede biraz beyin jimnastiği yaptırmış oldum :upside_down_face: Teşekkür ediyorum yardımınız için

1 Beğeni

Hizli olsun diye verdigim yanlis ornek ortaligi karistirdi. Soyle toparlayalim:

Cookie’nin turu (session/permanent) onemli degil. Ne zaman silinmesi gerektigine dair bir ipucu sadece.

Cookie icerigi olarak:

  1. Session ID. Database’e “zkvhjdsf: user=aib” yaziyoruz, cookie’ye “session_id=zkvhjdsf” yaziyoruz. Diger session ID’leri tahmin edilemiyor.
  2. JWT. Cookie’ye “user=aib” yazip kriptografik imzaliyoruz. Diger cookie icerikleri imzalanamiyor.
2 Beğeni

Hocam ben Brave’den helmsys__ hesabına giriş yapıyorum. Daha sonra açtığım Microsoft Edgeden de helmsys__ hesabına ait url yi kopyala yapıştır yaptığımda beni ana sayfaya yönlendiriyor. Yani Braveden gidiğim hesaba Edgeden giriş yapamıyorum. Tam istediğim gibi. Fakat ortaya şöyle yeni bir sorun çıkıyor.

Braveden helmsys__ hesabına giriş yapıyorum. Microsoft Edgeyi açıp oradan da arif_cem hesabına giriş yapıyorum;
Brave de http://localhost:5000/login/r/helmsys__ sayfamdayım
Edge de http://localhost:5000/login/r/arif_cem sayfasındayım.

Mantık olarak Edgede olduğum sayfaya cookie sayesinde Braveden erişememem lazım. Yani braveye http://localhost:5000/login/r/arif_cem yazdığımda sayfaya ulaşmak yerine ana sayfaya yönlendirmeli.
Fakat böyle olmuyor ve sayfaya giriş yapabiliyor. Şimdi burdan yola çıkarak diğer bir sorunda ortaya çıkıyor. Edgeden giriş yaptığım sayfaya Braveden de giriş yapabildiğim için, artık http://localhost:5000/login/r/helmsys__ yazsam da beni http://localhost:5000/login/r/arif_cem sayfasına yönlendiriyor yani onun hesabına girmiş oluyorum.

Nasıl bir yol izlemem gerekiyor ?

Bu arada dediklerinizi bir kez daha okursam anlayacağımı düşünüyorum :sweat_smile:

Benim beynim yanıyor hocam.