File size: 4,421 Bytes
4585d4c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import streamlit as st
from typing import Dict, List, Callable, Optional

from utils.auth_utils import run_one_round_authentication

def one_round_auth(
    secret: str,
    direction_mapping: Dict[str, str],
    colors: List[str],
    direction_map: Dict[str, str],
    domains: Dict[str, Dict],
    session_key: str = "one_round_auth",
    on_success: Optional[Callable] = None,
    on_failure: Optional[Callable] = None,
    show_reference: bool = True
) -> bool:
    """
    Streamlit component for one-round 1P authentication.

    Args:
        secret: The user's secret character
        direction_mapping: Mapping of colors to directions
        colors: List of available colors
        direction_map: Mapping of direction names to codes
        domains: Available character domains
        session_key: Unique key for session state
        on_success: Optional callback function when auth succeeds
        on_failure: Optional callback function when auth fails
        show_reference: Whether to show direction mapping reference

    Returns:
        True if authentication is completed successfully, False otherwise
    """
    # Initialize session state for this component
    if session_key not in st.session_state:
        st.session_state[session_key] = {
            'started': False,
            'completed': False,
            'success': False,
            'grid_html': None,
            'expected': None
        }

    sess = st.session_state[session_key]

    # If not started yet, show start button
    if not sess['started']:
        st.info("Click 'Authenticate' to verify your identity")
        if st.button("🔐 Authenticate", type="primary", key=f"{session_key}_start_btn"):
            # Generate challenge
            grid_html, expected = run_one_round_authentication(
                secret, direction_mapping, colors, direction_map, domains
            )

            # Update session state
            sess['started'] = True
            sess['grid_html'] = grid_html
            sess['expected'] = expected
            st.rerun()
        return False

    # If already completed, return result
    if sess['completed']:
        return sess['success']

    # Display the challenge grid
    st.markdown(sess['grid_html'], unsafe_allow_html=True)

    # Show direction mapping as reference if requested
    if show_reference:
        with st.expander("🧭 Your Direction Mapping Reference"):
            col1, col2 = st.columns(2)
            with col1:
                for color in colors[:len(colors)//2]:
                    direction = direction_mapping.get(color, "Skip")
                    emoji_map = {"Up": "⬆️", "Down": "⬇️", "Left": "⬅️", "Right": "➡️", "Skip": "⏭️"}
                    st.markdown(f"**{color.title()}**: {direction} {emoji_map[direction]}")
            with col2:
                for color in colors[len(colors)//2:]:
                    direction = direction_mapping.get(color, "Skip")
                    emoji_map = {"Up": "⬆️", "Down": "⬇️", "Left": "⬅️", "Right": "➡️", "Skip": "⏭️"}
                    st.markdown(f"**{color.title()}**: {direction} {emoji_map[direction]}")

    # Input for the direction
    col1, col2 = st.columns([3, 1])
    with col1:
        user_input = st.radio(
            "What direction do you see?",
            options=["⬆️ Up", "⬇️ Down", "⬅️ Left", "➡️ Right", "⏭️ Skip"],
            key=f"{session_key}_input",
            horizontal=True
        )

    with col2:
        st.markdown("<br>", unsafe_allow_html=True)  # Spacing
        if st.button("Submit", type="primary", key=f"{session_key}_submit_btn"):
            # Map emoji selection to direction code
            direction_code = {
                "⬆️ Up": "U",
                "⬇️ Down": "D",
                "⬅️ Left": "L",
                "➡️ Right": "R",
                "⏭️ Skip": "S"
            }[user_input]

            # Check if the answer is correct
            success = direction_code == sess['expected']

            # Update session state
            sess['completed'] = True
            sess['success'] = success

            # Call callbacks if provided
            if success and on_success is not None:
                on_success()
            elif not success and on_failure is not None:
                on_failure()

            st.rerun()

    return False