whs-authz-authn/browser-use-oauth
Watch
1
0
Fork
You've already forked browser-use-oauth
0
mirror of https://github.com/j93es/browser-use-oauth.git synced 2026-10-12 07:21:21 +09:00

Merge pull request #21 from j93es/feat/j93es

Feat/j93es
This commit is contained in:
James 2025-06-27 10:26:42 +09:00 • committed by GitHub
commit 8c22a52774
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 451 additions and 378 deletions

9
lib/agents/__init__.py Normal file
View file

@ -0,0 +1,9 @@
from lib.agents.get_sso_list import get_sso_list
# 업데이트될 버전 import 아직 개발 중
from lib.agents.get_sso_list_v2 import get_sso_list as get_sso_list_v2
from lib.agents.login_google import login_google
__all__ = [
"get_sso_list",
"login_google",
]

View file

@ -1,92 +0,0 @@
import json
from pydantic import BaseModel
from browser_use import (
Agent,
Controller,
)
from lib.agents.run_agent import run_agent
from lib.utils.logger import logger
from lib.browser_use_utils.create_google_ai import create_google_ai
from lib.config import GOOGLE_MODEL, GOOGLE_PLANNER_MODEL
NOT_FOUND_LOGIN_PAGE = 0
FOUND_LOGIN_PAGE = 1
class FindLoginPageResponse(BaseModel):
status: int = NOT_FOUND_LOGIN_PAGE # 0 if not found, 1 if found
msg: str | None = None
url: str | None = None
async def find_login_page(target_url, session) -> tuple[bool, str | None]:
initial_actions = [{"open_tab": {"url": target_url}}]
task = """
You are an expert in finding login pages.
Your task is to navigate to the login page of the given URL. Follow the steps below strictly and return results only in the specified format.
※ You are NOT allowed to navigate to URLs that are not directly discoverable within the initial domain. Do NOT use search engines or guess external login URLs.
0. INITIAL BLOCK CHECK
- If the browser is blocked when trying to access the page — due to firewall, CAPTCHA, regional restrictions, or other access denials — immediately terminate the process and return the following JSON:
```json
{
"status": 0,
"msg": "Blocked",
"url": ""
}
```
- Do NOT proceed to further steps in this case.
1. LOGIN PAGE NAVIGATION
- Navigate only to a **client-side (non-enterprise)** login page within the provided domain.
- Do NOT rely on external tools, search engines, or links not directly found on the site.
- If a consent popup (e.g. for privacy/cookies) appears, you MUST dismiss or close it before proceeding.
- Since step 0 confirmed access, assume the page now loads properly.
2. RETURN FORMAT
- Once the login page is reached, return a JSON object matching the following schema:
```json
{
"status": 1, // 1 if login page is found, 0 otherwise
"msg": "Login page found", // Optional message
"url": "https://example.com/login" // Full URL of the login page if found
}
```
- If the login page cannot be found, return:
```json
{
"status": 0,
"msg": "Login page not found",
"url": ""
}
```
- Return ONLY the JSON object. Do NOT include any explanation, logging, or extra output.
"""
controller = Controller(output_model=FindLoginPageResponse, exclude_actions=['search_google'])
agent = Agent(
browser_session=session,
initial_actions=initial_actions,
task=task,
llm=create_google_ai(GOOGLE_MODEL),
controller=controller,
)
is_failed, final_result = await run_agent(agent)
if is_failed:
logger(f"⚠️ 스캔 실패: {target_url} | {final_result}")
print(f"⚠️ 스캔 실패: {target_url} | {final_result}")
return False, None;
data = json.loads(final_result)
try:
resp = FindLoginPageResponse(**data)
if resp.status == FOUND_LOGIN_PAGE and len(resp.url) > 0:
return True, resp.url
else:
return False, resp.msg
except Exception as e:
logger(f"⚠️ 결과 파싱 실패: {target_url} | {e}\n원본 결과: {data.msg}")
print(f"⚠️ 결과 파싱 실패: {target_url} | {e}\n원본 결과: {data.msg}")
return False, data.msg

View file

@ -1,66 +0,0 @@
import json
from pydantic import BaseModel
from browser_use import (
Agent,
Controller,
)
from lib.agents.run_agent import run_agent
from lib.utils.logger import logger
from lib.browser_use_utils.create_google_ai import create_google_ai
from lib.config import GOOGLE_MODEL, GOOGLE_PLANNER_MODEL
NOT_FOUND_SSO_LIST = 0
FOUND_SSO_LIST = 1
class EachSSOProvider(BaseModel):
provider: str
oauth_uri: str | None = None
class FindLoginPageResponse(BaseModel):
EachSSOProviders: list[EachSSOProvider] | None = None
status: int = NOT_FOUND_SSO_LIST # 0 if not found,
msg: str | None = None
async def get_sso_list(target_url, session) -> tuple[bool, str | None]:
initial_actions = [{"open_tab": {"url": target_url}}]
task = "Navigate to the login page, and return the result in the specified format."
extend_planner_system_message = """
You are an expert in finding login pages.
Your task is to navigate to the login page of the given URL.
Once you reach the login page, stop and return a JSON object that matches the following schema:
```json
{
"status": 1, # 1 if login page found, 0 otherwise
"url": "https://example.com/login" # Full URL of the login page if found
}
Return only this JSON object. Do not include any explanation or additional text.
"""
controller = Controller(output_model=FindLoginPageResponse, exclude_actions=['search_google'])
agent = Agent(
browser_session=session,
initial_actions=initial_actions,
task=task,
llm=create_google_ai(GOOGLE_MODEL),
planner_llm=create_google_ai(GOOGLE_PLANNER_MODEL),
controller=controller,
extend_planner_system_message=extend_planner_system_message,
)
is_failed, final_result = await run_agent(agent)
if is_failed:
logger(f"⚠️ 스캔 실패: {target_url} | {final_result}")
print(f"⚠️ 스캔 실패: {target_url} | {final_result}")
return False, None;
data = json.loads(final_result)
try:
resp = FindLoginPageResponse(**data)
if resp.status == FOUND_SSO_LIST:
return True, resp
else:
return False, None
except Exception as e:
logger(f"⚠️ 결과 파싱 실패: {target_url} | {e}\n원본 결과: {data.msg}")
print(f"⚠️ 결과 파싱 실패: {target_url} | {e}\n원본 결과: {data.msg}")
return False, data.msg

View file

@ -0,0 +1,3 @@
from lib.agents.get_sso_list.get_sso_list import get_sso_list
__all__ = ["get_sso_list"]

View file

@ -0,0 +1,22 @@
from lib.agents.get_sso_list.prompt import get_sso_list_task, FindLoginPageResponse
from lib.browser_use_utils.run_task import run_task
NOT_FOUND_LOGIN_PAGE = 0
FOUND_LOGIN_PAGE = 1
async def get_sso_list(target_url) -> tuple[bool, str | FindLoginPageResponse | None]:
task = get_sso_list_task
ReturnModel = FindLoginPageResponse
success, response = await run_task(target_url, ReturnModel, task)
if not success:
return False, response
if isinstance(response, str):
return False, response
return True, response

View file

@ -0,0 +1,68 @@
from pydantic import BaseModel
class FindLoginPageResponse(BaseModel):
msg: str | None = None
url: str | None = None
sso_list: list[str] = [] # List of SSO providers found on the login page
get_sso_list_task = """
You are an expert in finding login pages.
Your task is to navigate to the login page of the given URL. Follow the steps below strictly and return results only in the specified format.
※ You are NOT allowed to navigate to URLs that are not directly discoverable within the initial domain. Do NOT use search engines or guess external login URLs.
0. INITIAL BLOCK CHECK
- If the browser is blocked when trying to access the page — due to firewall, CAPTCHA, regional restrictions, or other access denials — immediately terminate the process and return the following JSON:
```json
{
"msg": "Blocked",
"url": "",
"sso_list": []
}
```
- Do NOT proceed to further steps in this case.
1. LOGIN PAGE NAVIGATION
- Navigate only to a **client-side (non-enterprise)** login page within the provided domain.
- Do NOT rely on external tools, search engines, or links not directly found on the site.
- If a consent popup (e.g. for privacy/cookies) appears, you MUST dismiss or close it before proceeding.
- Since step 0 confirmed access, assume the page now loads properly.
2. SSO BUTTON IDENTIFICATION
- On the login page, look for the following social login (SSO) buttons:
- Google, GitHub, Facebook, LinkedIn, Microsoft, Naver, Slack, Etc.
- ✅ Proceed only if it is clearly an **actual SSO button**.
- ❌ Exclude the following:
- Passkey-related buttons
- Username/password fields
- Email-based login
- Non-OAuth methods such as certificate or phone verification
3. RETURN FORMAT
- If the login page is successfully found, return:
```json
{
"msg": "Login page found",
"url": "https://example.com/login",
"sso_list": ["Google", "GitHub"]
}
```
- If the login page cannot be found, return:
```json
{
"msg": "Login page not found",
"url": "",
"sso_list": []
}
```
- If blocked (as in step 0), return:
```json
{
"msg": "Blocked",
"url": "",
"sso_list": []
}
```
- Return ONLY the JSON object. Do NOT include any explanation, logging, or extra output.
"""

View file

@ -0,0 +1,3 @@
from lib.agents.get_sso_list_v2 import get_sso_list
__all__ = ["get_sso_list"]

View file

@ -0,0 +1,20 @@
from lib.agents.get_sso_list_v2.prompt import get_sso_list_task, FindLoginPageResponse
from lib.browser_use_utils.run_task import run_task
# TODO - Split find login page agent and get SSO list agent
async def get_sso_list(target_url) -> tuple[bool, str | FindLoginPageResponse | None]:
task = get_sso_list_task
ReturnModel = FindLoginPageResponse
success, response = await run_task(target_url, ReturnModel, task)
if not success:
return False, response
if isinstance(response, str):
return False, response
return True, response

View file

@ -0,0 +1,68 @@
from pydantic import BaseModel
class FindLoginPageResponse(BaseModel):
msg: str | None = None
url: str | None = None
sso_list: list[str] = [] # List of SSO providers found on the login page
get_sso_list_task = """
You are an expert in finding login pages.
Your task is to navigate to the login page of the given URL. Follow the steps below strictly and return results only in the specified format.
※ You are NOT allowed to navigate to URLs that are not directly discoverable within the initial domain. Do NOT use search engines or guess external login URLs.
0. INITIAL BLOCK CHECK
- If the browser is blocked when trying to access the page — due to firewall, CAPTCHA, regional restrictions, or other access denials — immediately terminate the process and return the following JSON:
```json
{
"msg": "Blocked",
"url": "",
"sso_list": []
}
```
- Do NOT proceed to further steps in this case.
1. LOGIN PAGE NAVIGATION
- Navigate only to a **client-side (non-enterprise)** login page within the provided domain.
- Do NOT rely on external tools, search engines, or links not directly found on the site.
- If a consent popup (e.g. for privacy/cookies) appears, you MUST dismiss or close it before proceeding.
- Since step 0 confirmed access, assume the page now loads properly.
2. SSO BUTTON IDENTIFICATION
- On the login page, look for the following social login (SSO) buttons:
- Google, GitHub, Facebook, LinkedIn, Microsoft, Naver, Slack, Etc.
- ✅ Proceed only if it is clearly an **actual SSO button**.
- ❌ Exclude the following:
- Passkey-related buttons
- Username/password fields
- Email-based login
- Non-OAuth methods such as certificate or phone verification
3. RETURN FORMAT
- If the login page is successfully found, return:
```json
{
"msg": "Login page found",
"url": "https://example.com/login",
"sso_list": ["Google", "GitHub"]
}
```
- If the login page cannot be found, return:
```json
{
"msg": "Login page not found",
"url": "",
"sso_list": []
}
```
- If blocked (as in step 0), return:
```json
{
"msg": "Blocked",
"url": "",
"sso_list": []
}
```
- Return ONLY the JSON object. Do NOT include any explanation, logging, or extra output.
"""

View file

@ -0,0 +1,3 @@
from lib.agents.login_google.login_google import login_google
__all__ = ["login_google"]

View file

@ -0,0 +1,11 @@
from lib.agents.login_google.prompt import login_google_task, LoginGoogleResponse
from lib.browser_use_utils.run_task import run_task
async def login_google(target_url) -> tuple[bool, str | LoginGoogleResponse | None]:
task = login_google_task
ReturnModel = LoginGoogleResponse
success, response = await run_task(target_url, ReturnModel, task)
if not success:
return False, None
return True, response

View file

@ -0,0 +1,63 @@
from pydantic import BaseModel
from lib.config import GOOGLE_ID, GOOGLE_PASSWORD
class LoginGoogleResponse(BaseModel):
msg: str | None = None
status: str | None = None # "success", "mfa_required", "google_blocked", "sso_not_found", "login_page_not_found", "invalid_credentials"
final_url: str | None = None
login_google_task = f"""
You are a web automation agent.
Your task is to visit the given domain and perform a full login via the **Google SSO button**, following all steps strictly as described below.
▶ Target: Find a login page inside this domain that allows "Sign in with Google", and use it to complete login via Google.
Instructions:
1. If any cookie or privacy popups appear, dismiss or accept them.
2. Navigate through the site's UI to find the **login or sign-in page** (e.g., via buttons like "Log In", "Sign In", "Get Started").
- Only follow links within the same domain.
3. On the login page, look for a clearly labeled **Google SSO button** — typically labeled as:
- "Continue with Google"
- "Sign in with Google"
- or a button with the Google 'G' icon
4. Click the **Google login button**.
- ⚠️ The Google login flow MUST open in a **new browser tab** (not a new window or popup).
- ❌ If the login opens in a new **window** or **popup**, do NOT continue. Immediately stop and return the appropriate status.
5. Check if the user is **already logged in to Google and immediately redirected back to the original site** without showing a Google login screen.
- ✅ If so, treat the login as successful and return immediately.
6. If redirected to the Google login page:
- If a **CAPTCHA**, **MFA prompt**, or a request for **ID/password entry** appears, do NOT proceed.
- Immediately stop and return the appropriate status.
7. If login proceeds without interruptions, wait for redirection back to the original site and record the final URL.
Credentials to use for Google login:
- Email: {GOOGLE_ID}
- Password: {GOOGLE_PASSWORD}
Constraints:
- Do NOT use search engines or guess URLs.
- Do NOT use autofill, saved sessions, or cookies.
- Do NOT proceed with login if:
- The login opens in a new window (only tabs are allowed)
- CAPTCHA or MFA appears
- ID/password input is required
- If the user is already logged in to Google and redirected back automatically, stop there and report success.
- If the login page cannot be found, return "login_page_not_found".
- If the Google login button is not found, return "sso_not_found".
- If a page such as a sign-up page appears, consider it a successful login and terminate immediately.
Final Output:
Return the result in the following format only:
```json
{{
"msg": "Google login completed",
"status": "success" | "already_logged_in" | "mfa_required" | "captcha_triggered" | "window_blocked" | "idpw_required" | "google_blocked" | "sso_not_found" | "login_page_not_found",
"final_url": "<url_after_login_redirect or empty string>"
}}
```
- Return ONLY the JSON object. Do NOT include any explanation, logging, or extra output.
"""

View file

@ -1,19 +0,0 @@
from lib.browser_use_utils.clean_resources import clean_agent_resources
async def run_agent(agent) -> tuple[int, str]:
try:
response = await agent.run()
final_result = response.final_result()
if final_result is None:
return -1, "최종 결과가 없습니다. 에이전트 실행 실패"
return 0, final_result
except Exception as e:
# API 쿼터 문제인지 확인
if "ResourceExhausted" in str(e) or "429" in str(e):
return 1, "API 쿼터 에러로 인한 실패"
# 일반 에러 처리
else:
return 2, "일반 에러로 인한 실패"
finally:
await clean_agent_resources(agent)

View file

@ -0,0 +1,15 @@
from lib.browser_use_utils.clean_resources import clean_resources, clean_agent_resources, clean_session_resources
from lib.browser_use_utils.create_google_ai import create_google_ai
from lib.browser_use_utils.get_profile import get_profile
from lib.browser_use_utils.run_agent import run_agent
from lib.browser_use_utils.run_task import run_task
__all__ = [
"clean_resources",
"clean_agent_resources",
"clean_session_resources",
"create_google_ai",
"get_profile",
"run_agent",
"run_task",
]

View file

@ -0,0 +1,40 @@
from typing import Any
from pydantic import BaseModel
from lib.browser_use_utils.clean_resources import clean_agent_resources
from lib.config import GOOGLE_MODEL
from browser_use import (
Agent,
Controller,
)
from lib.browser_use_utils.create_google_ai import create_google_ai
async def run_agent(session, initial_actions, ReturnModel: type[BaseModel], task: str) -> tuple[bool, str, Any | None]:
controller = Controller(output_model=ReturnModel, exclude_actions=['search_google'])
agent = Agent(
browser_session=session,
initial_actions=initial_actions,
task=task,
llm=create_google_ai(GOOGLE_MODEL),
controller=controller,
)
try:
response = await agent.run()
final_result = response.final_result()
if final_result is None:
return False, "LLM이 반환한 최종 결과가 없습니다.", None
except Exception as e:
# API 쿼터 문제인지 확인
if "ResourceExhausted" in str(e) or "429" in str(e):
return False, "API 쿼터 에러로 인한 실패", None
# 일반 에러 처리
else:
return False, "일반 에러로 인한 실패", None
finally:
await clean_agent_resources(agent)
return True, "ok", final_result

View file

@ -0,0 +1,40 @@
import json
from typing import Any
from pydantic import BaseModel
from browser_use import (
BrowserSession
)
from patchright.async_api import async_playwright as async_patchright
from lib.utils.logger import logger
from lib.browser_use_utils import get_profile, clean_session_resources, run_agent
async def run_task(target_url: str, ReturnModel: type[BaseModel], task: str) -> tuple[bool, type[BaseModel] | None]:
session = BrowserSession(
playwright=(await async_patchright().start()),
browser_profile=await get_profile(),
)
initial_actions = [{"open_tab": {"url": target_url}}]
seccess, msg, final_result = await run_agent(session=session,
initial_actions=initial_actions,
ReturnModel=ReturnModel,
task=task)
if not seccess:
logger(f"⚠️ LLM 실행 실패: {target_url} | {msg}")
print(f"⚠️ LLM 실행 실패: {target_url} | {msg}")
await clean_session_resources(session)
return False, None
try:
data = json.loads(final_result)
resp = ReturnModel(**data)
return True, resp
except Exception as e:
logger(f"⚠️ LLM 응답 결과 파싱 실패: {target_url} | {e}\n원본 결과: {data.msg}")
print(f"⚠️ LLM 응답 결과 파싱 실패: {target_url} | {e}\n원본 결과: {data.msg}")
return False, None
finally:
await clean_session_resources(session)

View file

@ -4,5 +4,7 @@ load_dotenv(verbose=True, override=True)
BACKEND_URL = os.getenv("BACKEND_URL", "http://localhost:11081")
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
GOOGLE_MODEL = os.getenv("GOOGLE_MODEL", "gemini-2.5-flash-preview-05-20")
GOOGLE_PLANNER_MODEL = os.getenv("GOOGLE_PLANNER_MODEL", "gemini-2.5-pro-preview-06-05")
GOOGLE_MODEL = os.getenv("GOOGLE_MODEL", "gemini-2.5-flash")
GOOGLE_ID = os.getenv("GOOGLE_ID", "google")
GOOGLE_PASSWORD = os.getenv("GOOGLE_PASSWORD", "google")

View file

@ -1,65 +0,0 @@
import asyncio
from browser_use import Agent, BrowserSession
from patchright.async_api import async_playwright as async_patchright
from lib.agents.find_login_page import find_login_page
from lib.browser_use_utils.clean_resources import clean_session_resources
from lib.browser_use_utils.get_profile import get_profile
from lib.utils.save_oauth_providers import save_oauth_providers
async def find_sso_list(target_url):
session = BrowserSession(
playwright=(await async_patchright().start()),
browser_profile=await get_profile(),
)
FIND_LOGIN_PAGE = 1
FIND_SSO_LIST = 2
SAVE_DATA = 3
WHEN_ERROR = -1
FINISH = 0
final_result = None
login_url = target_url
state = FIND_LOGIN_PAGE
while True:
if state == FIND_LOGIN_PAGE:
is_success, resp = await find_login_page(
target_url=target_url,
session=session,
)
if not is_success:
print(f"⚠️ 로그인 페이지 탐지 실패: {target_url} | {resp}")
state = WHEN_ERROR
login_url = resp if resp else target_url
state = FIND_SSO_LIST
if state == FIND_SSO_LIST:
print(f"🔎 SSO 목록 찾는 중: {target_url}")
is_success, resp = await find_sso_list(
target_url=login_url,
session=session,
)
if not is_success:
print(f"⚠️ SSO 목록 탐지 실패: {target_url} | {resp}")
state = WHEN_ERROR
final_result = ""
state = SAVE_DATA
if state == SAVE_DATA:
print(f"💾 데이터 저장 중: {target_url}")
if not final_result:
print(f"⚠️ SSO 목록이 전달되지 않았습니다: {target_url}")
state = WHEN_ERROR
save_oauth_providers(target_url, final_result)
state = FINISH
if state == WHEN_ERROR:
print(f"⚠️ 에러 발생: {target_url} | 스캔을 중단합니다.")
return
if state == FINISH:
print(f"✅ 스캔 완료: {target_url}")
break
await clean_session_resources(session)

20
lib/utils/__init__.py Normal file
View file

@ -0,0 +1,20 @@
from lib.utils.env_checker import check_env_variables
from lib.utils.is_html import is_html_url
from lib.utils.logger import logger
from lib.utils.notify_backend import notify_backend
from lib.utils.progress_checker import save_progress, load_progress
# v2 import => 아직 개발 중
from lib.utils.progress_checker_v2 import ProgressChecker
from lib.utils.read_txt import read_lines_between
from lib.utils.save_oauth_providers import save_oauth_providers
__all__ = [
"check_env_variables",
"is_html_url",
"logger",
"notify_backend",
"read_lines_between",
"save_progress",
"load_progress",
"save_oauth_providers",
]

View file

@ -8,7 +8,6 @@ def check_env_variables():
"BACKEND_URL",
"GOOGLE_API_KEY",
"GOOGLE_MODEL",
"GOOGLE_PLANNER_MODEL"
]
for var in required_vars:

Some files were not shown because too many files have changed in this diff Show more