1+ package jazzyframework .security ;
2+
3+ import jazzyframework .di .DIContainer ;
4+ import jazzyframework .routing .Router ;
5+ import jazzyframework .security .annotations .EnableJazzyAuth ;
6+ import jazzyframework .security .controller .DefaultAuthController ;
7+ import jazzyframework .security .jwt .JwtUtil ;
8+ import jazzyframework .security .validation .UserEntityValidator ;
9+ import jazzyframework .security .config .SecurityConfig ;
10+ import jazzyframework .data .BaseRepository ;
11+
12+ import java .util .Set ;
13+ import java .util .logging .Logger ;
14+
15+ /**
16+ * Processes @EnableJazzyAuth annotation and automatically registers authentication endpoints.
17+ *
18+ * <p>This processor is responsible for:
19+ * <ul>
20+ * <li>Scanning for @EnableJazzyAuth annotations on application classes</li>
21+ * <li>Validating user entity classes for required authentication fields</li>
22+ * <li>Creating and configuring JWT utilities</li>
23+ * <li>Registering authentication endpoints (register, login, me)</li>
24+ * <li>Setting up SecurityInterceptor when SecurityConfig is available</li>
25+ * </ul>
26+ *
27+ * <p>The processor automatically integrates with the DI container to:
28+ * <ul>
29+ * <li>Obtain user repository instances</li>
30+ * <li>Register authentication controllers</li>
31+ * <li>Configure security interceptors</li>
32+ * </ul>
33+ *
34+ * @since 0.5.0
35+ * @author Caner Mastan
36+ */
37+ public class AuthProcessor {
38+
39+ private static final Logger logger = Logger .getLogger (AuthProcessor .class .getName ());
40+ private final Router router ;
41+ private final DIContainer diContainer ;
42+ private JwtUtil jwtUtil ; // Store for SecurityInterceptor
43+
44+ /**
45+ * Creates a new AuthProcessor with the specified router and DI container.
46+ *
47+ * @param router The router to register authentication endpoints
48+ * @param diContainer The DI container for dependency management
49+ */
50+ public AuthProcessor (Router router , DIContainer diContainer ) {
51+ this .router = router ;
52+ this .diContainer = diContainer ;
53+ }
54+
55+ /**
56+ * Scans for @EnableJazzyAuth annotation and configures authentication.
57+ *
58+ * <p>This method performs the following steps:
59+ * <ol>
60+ * <li>Scans all registered classes in the DI container</li>
61+ * <li>Looks for classes annotated with @EnableJazzyAuth</li>
62+ * <li>Configures authentication based on the annotation parameters</li>
63+ * <li>Falls back to classpath scanning if needed</li>
64+ * </ol>
65+ */
66+ public void processAuthAnnotations () {
67+ try {
68+ // Get all registered classes from DI container
69+ Set <Class <?>> allClasses = diContainer .getAllRegisteredClasses ();
70+
71+ for (Class <?> clazz : allClasses ) {
72+ if (clazz .isAnnotationPresent (EnableJazzyAuth .class )) {
73+ EnableJazzyAuth authConfig = clazz .getAnnotation (EnableJazzyAuth .class );
74+ configureAuthentication (authConfig );
75+ logger .info ("Authentication configured from @EnableJazzyAuth on " + clazz .getSimpleName ());
76+ return ; // Only process first found annotation
77+ }
78+ }
79+
80+ // Also scan all classes in classpath for @EnableJazzyAuth (fallback approach)
81+ scanClasspathForAuthAnnotation ();
82+
83+ } catch (Exception e ) {
84+ logger .warning ("Failed to process @EnableJazzyAuth annotations: " + e .getMessage ());
85+ e .printStackTrace ();
86+ }
87+ }
88+
89+ /**
90+ * Scans classpath for @EnableJazzyAuth annotation using stack trace analysis.
91+ * This is a fallback method when the annotation is not found in DI container.
92+ */
93+ private void scanClasspathForAuthAnnotation () {
94+ try {
95+ // Get stack trace to find the main class
96+ StackTraceElement [] stackTrace = Thread .currentThread ().getStackTrace ();
97+
98+ for (StackTraceElement element : stackTrace ) {
99+ if ("main" .equals (element .getMethodName ())) {
100+ try {
101+ Class <?> mainClass = Class .forName (element .getClassName ());
102+ if (mainClass .isAnnotationPresent (EnableJazzyAuth .class )) {
103+ EnableJazzyAuth authConfig = mainClass .getAnnotation (EnableJazzyAuth .class );
104+ configureAuthentication (authConfig );
105+ logger .info ("Authentication configured from @EnableJazzyAuth on " + mainClass .getSimpleName ());
106+ return ;
107+ }
108+ } catch (ClassNotFoundException e ) {
109+ // Continue scanning
110+ }
111+ }
112+ }
113+
114+ logger .fine ("No @EnableJazzyAuth annotation found" );
115+ } catch (Exception e ) {
116+ logger .warning ("Failed to scan classpath for @EnableJazzyAuth: " + e .getMessage ());
117+ }
118+ }
119+
120+ /**
121+ * Configures authentication based on @EnableJazzyAuth annotation parameters.
122+ *
123+ * <p>This method:
124+ * <ul>
125+ * <li>Validates the user entity class</li>
126+ * <li>Creates JWT utility with specified configuration</li>
127+ * <li>Obtains user repository from DI container</li>
128+ * <li>Creates and registers authentication controller</li>
129+ * <li>Registers authentication endpoints</li>
130+ * <li>Sets up security interceptor if SecurityConfig exists</li>
131+ * </ul>
132+ *
133+ * @param config The @EnableJazzyAuth annotation configuration
134+ * @throws RuntimeException if authentication configuration fails
135+ */
136+ private void configureAuthentication (EnableJazzyAuth config ) {
137+ try {
138+ // Validate user class
139+ UserEntityValidator .validateUserClass (config .userClass (), config .loginMethod ());
140+
141+ // Create JWT utility
142+ jwtUtil = new JwtUtil (config .jwtSecret (), config .jwtExpirationHours ());
143+
144+ // Get user repository from DI container
145+ BaseRepository <Object , Long > userRepository = getUserRepository (config .repositoryClass ());
146+
147+ // Create auth controller
148+ DefaultAuthController authController = new DefaultAuthController (
149+ config .userClass (),
150+ config .loginMethod (),
151+ jwtUtil ,
152+ userRepository
153+ );
154+
155+ // Register auth controller in DI container
156+ diContainer .registerInstance (DefaultAuthController .class , authController );
157+
158+ // Register auth endpoints
159+ String basePath = config .authBasePath ();
160+ router .POST (basePath + "/register" , "register" , DefaultAuthController .class );
161+ router .POST (basePath + "/login" , "login" , DefaultAuthController .class );
162+ router .GET (basePath + "/me" , "getCurrentUser" , DefaultAuthController .class );
163+
164+ logger .info ("Authentication endpoints registered:" );
165+ logger .info (" POST " + basePath + "/register" );
166+ logger .info (" POST " + basePath + "/login" );
167+ logger .info (" GET " + basePath + "/me" );
168+ logger .info ("Using repository: " + config .repositoryClass ().getSimpleName ());
169+
170+ // Set up SecurityInterceptor if SecurityConfig exists
171+ setupSecurityInterceptor ();
172+
173+ } catch (Exception e ) {
174+ throw new RuntimeException ("Failed to configure authentication" , e );
175+ }
176+ }
177+
178+ /**
179+ * Sets up SecurityInterceptor if a SecurityConfig implementation is found in the DI container.
180+ *
181+ * <p>This method scans for classes extending SecurityConfig and creates a SecurityInterceptor
182+ * instance to handle URL-based security rules.
183+ */
184+ private void setupSecurityInterceptor () {
185+ try {
186+ // Look for SecurityConfig in DI container
187+ Set <Class <?>> allClasses = diContainer .getAllRegisteredClasses ();
188+
189+ for (Class <?> clazz : allClasses ) {
190+ if (SecurityConfig .class .isAssignableFrom (clazz ) && !SecurityConfig .class .equals (clazz )) {
191+ SecurityConfig securityConfig = (SecurityConfig ) diContainer .getBean (clazz );
192+ SecurityInterceptor interceptor = new SecurityInterceptor (securityConfig , jwtUtil );
193+
194+ // Register interceptor in DI container for Router to use
195+ diContainer .registerInstance (SecurityInterceptor .class , interceptor );
196+
197+ logger .info ("SecurityInterceptor configured with: " + clazz .getSimpleName ());
198+ return ;
199+ }
200+ }
201+
202+ logger .fine ("No SecurityConfig found, authentication endpoints only" );
203+ } catch (Exception e ) {
204+ logger .warning ("Failed to setup SecurityInterceptor: " + e .getMessage ());
205+ }
206+ }
207+
208+ /**
209+ * Gets the user repository from the DI container.
210+ *
211+ * @param repositoryClass The repository class to obtain
212+ * @return The repository instance cast to BaseRepository
213+ * @throws RuntimeException if the repository cannot be found or is invalid
214+ */
215+ @ SuppressWarnings ("unchecked" )
216+ private BaseRepository <Object , Long > getUserRepository (Class <?> repositoryClass ) {
217+ try {
218+ // Get the repository from DI container
219+ Object repository = diContainer .getBean (repositoryClass );
220+ if (repository instanceof BaseRepository ) {
221+ return (BaseRepository <Object , Long >) repository ;
222+ } else {
223+ throw new IllegalArgumentException ("Repository class must extend BaseRepository: " + repositoryClass .getSimpleName ());
224+ }
225+ } catch (Exception e ) {
226+ throw new RuntimeException ("Failed to get user repository: " + repositoryClass .getSimpleName () +
227+ ". Make sure it's registered in DI container." , e );
228+ }
229+ }
230+ }
0 commit comments