Integrar autenticación Cognito con Floci local y soporte AWS

- Añade servicios y configuración Cognito para login web (admins) y API (scanners)
- Soporta Floci cuando AWS_ENDPOINT_URL está definido; AWS real en caso contrario
- Incluye bootstrap Floci, docker-compose.local.yml y carga automática en run-local.sh
- Documenta arquitectura, flujos y arranque manual/IntelliJ en README
- Actualiza gitignore: excluye Flutter, H2 local y artefactos generados
This commit is contained in:
Alejandro Lara
2026-06-09 16:59:50 -06:00
parent f8468350d5
commit 59bd2d2d8a
116 changed files with 1733 additions and 4318 deletions
+45 -7
View File
@@ -1,17 +1,55 @@
/data/
/target/
# --- Maven / Java ---
target/
**/target/
/.idea/
/.vscode/
*.class
*.jar
*.war
*.ear
hs_err_pid*
replay_pid*
# --- Spring Boot / runtime local ---
**/data/h2/
*.mv.db
*.lock.db
*.trace.db
# --- Docker / Floci (generado en local) ---
/docker/floci/generated/*
!/docker/floci/generated/.gitkeep
# --- App Flutter (proyecto aparte, no versionar aquí) ---
/flutter-app/
# --- IDE ---
.idea/
*.iml
*.log
/.mvn/
/.DS_Store
*.ipr
*.iws
.vscode/
.classpath
.project
.settings/
**/.classpath
**/.project
**/.settings/
# --- Maven wrapper local (si se genera) ---
.mvn/wrapper/maven-wrapper.jar
# --- Logs y temporales ---
*.log
*.tmp
*.swp
*~
# --- OS ---
.DS_Store
Thumbs.db
# --- Maven auxiliar ---
effective-pom.xml
**/effective-pom.xml
# --- Datos locales en raíz (legacy) ---
/data/
+619 -112
View File
@@ -1,78 +1,362 @@
# Base Admin Web
Proyecto base de Spring Boot con Metronic, autenticacion local y estructura modular lista para reutilizar en nuevos paneles administrativos.
Proyecto base de Spring Boot con Metronic, autenticación Cognito (o local), panel administrativo y API de escaneo para la app móvil Flutter.
Sirve como plantilla reutilizable para nuevos paneles internos: estructura modular, seguridad dual (web + API), módulo de códigos pase con QR y soporte H2/PostgreSQL.
---
## Tabla de contenidos
- [Vista general](#vista-general)
- [Stack](#stack)
- [Arquitectura del sistema](#arquitectura-del-sistema)
- [Módulos Maven](#módulos-maven)
- [Seguridad y autenticación](#seguridad-y-autenticación)
- [Cognito: local (Floci) vs AWS](#cognito-local-floci-vs-aws)
- [Módulo de códigos pase](#módulo-de-códigos-pase)
- [API REST](#api-rest)
- [App Flutter (escáner)](#app-flutter-escáner)
- [Arranque local](#arranque-local)
- [Arranque manual e IntelliJ IDEA](#arranque-manual-e-intellij-idea)
- [Perfiles Spring](#perfiles-spring)
- [Variables de entorno](#variables-de-entorno)
- [Objetivo de esta base](#objetivo-de-esta-base)
---
## Vista general
El sistema tiene **dos canales de acceso** con roles distintos:
| Canal | Usuario | Rol | Autenticación |
|-------|---------|-----|---------------|
| Panel web (Thymeleaf) | `admin` | `ROLE_ADMIN` | Sesión HTTP (form login) |
| API móvil / externa | `scanner` | `ROLE_SCANNER` | JWT Bearer (stateless) |
Por defecto ambos canales usan **Amazon Cognito** (emulado en local con Floci). Con `APP_COGNITO_ENABLED=false` se usa autenticación local contra la base de datos y JWT firmado con secreto propio.
```mermaid
flowchart TB
subgraph clients["Clientes"]
Browser["Navegador<br/>Panel admin"]
Flutter["App Flutter<br/>QR Scanner"]
end
subgraph app["Spring Boot — base-admin-web-jar"]
WebChain["Filter chain web<br/>sesión + CSRF"]
ApiChain["Filter chain API<br/>stateless + JWT"]
PassCodes["PassCodeService"]
DB[("H2 / PostgreSQL")]
end
subgraph auth["Autenticación"]
Cognito["Cognito<br/>Floci local o AWS"]
LocalAuth["BD local + JWT HS256<br/>APP_COGNITO_ENABLED=false"]
end
Browser -->|"/login, /admin/**"| WebChain
Flutter -->|"/api/auth/login<br/>/api/pass-codes/scan"| ApiChain
WebChain --> PassCodes
ApiChain --> PassCodes
PassCodes --> DB
WebChain --> Cognito
ApiChain --> Cognito
WebChain -.-> LocalAuth
ApiChain -.-> LocalAuth
```
**URLs locales:**
| Recurso | URL |
|---------|-----|
| Login | http://localhost:8080/login |
| Dashboard | http://localhost:8080/dashboard |
| Códigos pase | http://localhost:8080/admin/pass-codes |
| H2 Console | http://localhost:8080/h2-console |
| Floci (Cognito local) | http://localhost:4566 |
---
## Stack
- Spring Boot 4.0.6
- Java 25
- Spring Security
- Thymeleaf
- JPA / Hibernate
- H2 en archivo para desarrollo rapido
- PostgreSQL listo por perfil
- JWT para autenticacion API de escaneo
- Assets Metronic reutilizados desde `base-web`
| Capa | Tecnología |
|------|------------|
| Backend | Spring Boot 4.0.6, Java 25 |
| Seguridad | Spring Security, OAuth2 Resource Server, AWS SDK Cognito |
| Vistas | Thymeleaf + Metronic (`base-web`) |
| Persistencia | JPA / Hibernate, H2 (dev), PostgreSQL (perfil `postgres`) |
| Auth | Cognito (default) o BD local + JWT HS256 |
| Móvil | Flutter (`flutter-app/qrscanner`) |
| Local AWS | [Floci](https://floci.io/floci/getting-started/quick-start/) (Cognito en puerto 4566) |
## Estructura
---
- `base-admin-web-domain`: entidades y repositorios
- `base-admin-web-application`: servicios y carga inicial
- `base-admin-web-configuration`: seguridad, JWT y perfiles
- `base-admin-web-web`: controladores y vistas Thymeleaf
- `base-admin-web-jar`: punto de entrada ejecutable
## Arquitectura del sistema
## Usuarios iniciales
### Capas y dependencias
### Administrador (panel web)
```mermaid
flowchart BT
jar["base-admin-web-jar<br/>punto de entrada + application.yml"]
web["base-admin-web-web<br/>controladores + Thymeleaf"]
config["base-admin-web-configuration<br/>seguridad, JWT, Cognito"]
app["base-admin-web-application<br/>servicios + carga inicial"]
domain["base-admin-web-domain<br/>entidades + repositorios"]
- Usuario: `admin`
- Password: `Admin123*`
- Rol: `ROLE_ADMIN`
- Acceso: login web en `/login`, dashboard y modulo de codigos pase
jar --> web
jar --> config
jar --> app
jar --> domain
web --> config
web --> app
config --> app
app --> domain
```
Variables de entorno:
| Módulo | Responsabilidad |
|--------|-----------------|
| `base-admin-web-domain` | Entidades JPA (`AppUser`, `PassCode`, `Role`), repositorios |
| `base-admin-web-application` | `PassCodeService`, `AppUserDetailsService`, `InitialDataLoader` |
| `base-admin-web-configuration` | `SecurityConfig`, `ApiSecurityConfig`, Cognito, `JwtConfig` |
| `base-admin-web-web` | Controladores MVC y API, DTOs, plantillas HTML |
| `base-admin-web-jar` | `BaseAdminWebApplication`, perfiles, dependencias runtime (H2, PostgreSQL) |
- `APP_ADMIN_USERNAME`
- `APP_ADMIN_PASSWORD`
- `APP_ADMIN_DISPLAY_NAME`
### Dos cadenas de seguridad
### Escaneador (API)
Spring Security define **dos filter chains** con distinto comportamiento:
- Usuario: `scanner`
- Password: `Scanner123*`
- Rol: `ROLE_SCANNER`
- Acceso: solo login por API y escaneo de codigos pase
```mermaid
flowchart LR
subgraph order1["@Order(1) — ApiSecurityConfig"]
A1["/api/**"]
A2["Stateless"]
A3["JWT Bearer"]
A4["ROLE_SCANNER en /scan"]
end
Variables de entorno:
subgraph order2["@Order(2) — SecurityConfig"]
B1["Todo excepto /api/**"]
B2["Sesión HTTP"]
B3["Form login /login"]
B4["ROLE_ADMIN en /admin/**"]
end
- `APP_SCANNER_USERNAME`
- `APP_SCANNER_PASSWORD`
- `APP_SCANNER_DISPLAY_NAME`
A1 --> A2 --> A3 --> A4
B1 --> B2 --> B3 --> B4
```
## Modulo de codigos pase
| Ruta | Acceso | Mecanismo |
|------|--------|-----------|
| `/login`, `/assets/**`, `/error`, `/h2-console/**` | Público | — |
| `/dashboard`, `/admin/**` | `ROLE_ADMIN` | Sesión web |
| `/api/auth/login`, `/api/auth/refresh` | Público | Emite tokens (solo `scanner`) |
| `/api/pass-codes/scan` | `ROLE_SCANNER` | JWT Bearer |
Panel administrativo para dar de alta y consultar codigos pase.
---
- URL: `http://localhost:8080/admin/pass-codes`
- Tambien accesible desde el boton **Codigos pase** en el dashboard
- Alta por nombre; el sistema genera un codigo aleatorio de 10 caracteres
- Vista con tabla de estatus, escaneo y fecha de creacion
- Modal Metronic para ver el QR de cada codigo
- Al cerrar el modal, la lista se actualiza automaticamente por si el codigo fue utilizado
## Seguridad y autenticación
Campos del codigo pase:
### Usuarios iniciales
- Nombre
- Codigo aleatorio
- Fecha de creacion
- Estatus (`ACTIVO` / `UTILIZADO`)
- Indicador de escaneado
- Fecha de escaneo
#### Administrador (panel web)
## API
- Usuario: `admin` / `Admin123*`
- Grupo Cognito: `admins``ROLE_ADMIN`
- Acceso: login en `/login`, dashboard, módulo de códigos pase
- El usuario `scanner` **no** puede entrar al panel web
### Login escaner
#### Escáner (API / Flutter)
- Usuario: `scanner` / `Scanner123*`
- Grupo Cognito: `scanners``ROLE_SCANNER`
- Acceso: `POST /api/auth/login` y `POST /api/pass-codes/scan`
- El usuario `admin` **no** puede autenticarse por API
### Flujo de login web (panel admin)
```mermaid
sequenceDiagram
actor Admin as Navegador (admin)
participant SC as SecurityConfig
participant CP as CognitoWebAuthenticationProvider
participant CA as CognitoUserAuthenticator
participant Cognito as Cognito (Floci/AWS)
Admin->>SC: POST /login (user, password)
SC->>CP: authenticate()
CP->>CA: admin client + ROLE_ADMIN
CA->>Cognito: InitiateAuth USER_PASSWORD_AUTH
Cognito-->>CA: tokens + cognito:groups
CA-->>CP: AuthenticatedWebUser (grupo admins)
CP-->>SC: sesión HTTP creada
SC-->>Admin: redirect /dashboard
```
**Clases clave:** `SecurityConfig`, `CognitoWebSecurityConfig`, `CognitoWebAuthenticationProvider`, `CognitoUserAuthenticator`, `AuthController`
Con `APP_COGNITO_ENABLED=false`, el flujo usa `DaoAuthenticationProvider``AppUserDetailsService` → tabla `users` (BCrypt).
### Flujo de login y escaneo API
```mermaid
sequenceDiagram
actor App as Flutter (scanner)
participant API as AuthApiController
participant AS as ApiAuthService
participant Cognito as Cognito
participant Scan as PassCodeApiController
participant Svc as PassCodeService
App->>API: POST /api/auth/login
API->>AS: login()
AS->>Cognito: scanner client + ROLE_SCANNER
Cognito-->>App: accessToken, refreshToken, idToken
App->>Scan: POST /api/pass-codes/scan<br/>Authorization: Bearer ...
Scan->>Scan: validar JWT (JWK o HS256)
Scan->>Svc: scan(code)
Svc-->>App: código marcado UTILIZADO
```
**Clases clave:** `ApiSecurityConfig`, `AuthApiController`, `ApiAuthService`, `CognitoAuthService`, `PassCodeApiController`, `JwtConfig`
### Modo local sin Cognito
Con `APP_COGNITO_ENABLED=false` (perfil `test` lo usa automáticamente):
```mermaid
flowchart LR
subgraph web["Web"]
W1["Form login"] --> W2["AppUser en BD"]
end
subgraph api["API"]
A1["POST /api/auth/login"] --> A2["BD + JwtEncoder HS256"]
A3["POST /api/auth/refresh"] --> A4["No disponible"]
A5["Bearer token"] --> A6["localJwtDecoder HS256"]
end
```
| `APP_COGNITO_ENABLED` | Web | API login | Validación JWT | Refresh |
|----------------------|-----|-----------|----------------|---------|
| `true` (default) | Cognito client admin | Cognito client scanner | JWK Set URI | Sí |
| `false` | BD + BCrypt | BD + JWT local | Secreto HS256 | No |
Los usuarios se siembran con `InitialDataLoader` usando `APP_ADMIN_*` y `APP_SCANNER_*`.
---
## Cognito: local (Floci) vs AWS
El SDK de Cognito elige el destino según `AWS_ENDPOINT_URL`:
```mermaid
flowchart TD
Start["APP_COGNITO_ENABLED=true"]
Check{"¿AWS_ENDPOINT_URL<br/>definido?"}
Floci["Floci localhost:4566<br/>credenciales test/test"]
AWS["Cognito real en AWS<br/>DefaultCredentialsProvider<br/>IAM / perfil / env AWS"]
Start --> Check
Check -->|Sí| Floci
Check -->|No| AWS
```
### Local con Floci
`./run-local.sh` hace todo el setup:
1. Levanta Floci (y PostgreSQL si el perfil lo incluye)
2. Espera el bootstrap de Cognito (hasta 120 s)
3. Carga `docker/floci/generated/cognito.local.env` automáticamente
El script `docker/floci/init/ready.d/10-cognito-bootstrap.sh` crea:
| Recurso | Valor |
|---------|-------|
| User Pool ID | `us-east-1_BaseAdminWeb` |
| Usuario admin | `admin` / `Admin123*` → grupo `admins` |
| Usuario scanner | `scanner` / `Scanner123*` → grupo `scanners` |
| App client admin | `base-admin-web-admin` |
| App client scanner | `base-admin-web-scanner` |
Variables generadas en `docker/floci/generated/cognito.local.env` (gitignored).
**Levantar solo Floci:**
```bash
./scripts/floci-up.sh
```
**Levantar stack Docker completo:**
```bash
docker compose -f docker-compose.local.yml up -d
```
| Servicio | Puerto | Persistencia |
|----------|--------|--------------|
| Floci | 4566 | volumen `base_admin_web_floci_data` |
| PostgreSQL | 5432 | volumen `base_admin_web_postgres_data` |
### Producción con AWS Cognito
**No definir** `AWS_ENDPOINT_URL`. Configura el User Pool real:
```bash
export APP_COGNITO_ENABLED=true
export COGNITO_REGION=us-east-1
export COGNITO_USER_POOL_ID=us-east-1_XXXXX
export COGNITO_ISSUER_URI=https://cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXX
export COGNITO_JWK_SET_URI=https://cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXX/.well-known/jwks.json
export COGNITO_ADMIN_CLIENT_ID=...
export COGNITO_SCANNER_CLIENT_ID=...
# Credenciales AWS vía IAM role, perfil o AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY
```
En AWS replica la misma estructura que Floci: grupos `admins`/`scanners`, clients con `USER_PASSWORD_AUTH` habilitado.
### Verificar Cognito local
```bash
export AWS_ENDPOINT_URL=http://localhost:4566
export AWS_DEFAULT_REGION=us-east-1
export AWS_ACCESS_KEY_ID=test
export AWS_SECRET_ACCESS_KEY=test
aws cognito-idp list-user-pools --max-results 10 --endpoint-url $AWS_ENDPOINT_URL
aws cognito-idp list-users --user-pool-id us-east-1_BaseAdminWeb --endpoint-url $AWS_ENDPOINT_URL
```
---
## Módulo de códigos pase
Panel para dar de alta y consultar códigos pase con QR.
```mermaid
stateDiagram-v2
[*] --> ACTIVO: admin crea código (10 chars)
ACTIVO --> UTILIZADO: scanner escanea QR
```
| Campo | Descripción |
|-------|-------------|
| Nombre | Etiqueta descriptiva |
| Código | Aleatorio de 10 caracteres (único) |
| Estatus | `ACTIVO` / `UTILIZADO` |
| Escaneado | Indicador booleano |
| Fechas | Creación y escaneo |
**Panel web:** `GET /admin/pass-codes` — tabla Metronic, modal QR, refresco automático al cerrar el modal.
**Entidad:** `PassCode`**Servicio:** `PassCodeService`**Controladores:** `PassCodeController` (web), `PassCodeApiController` (API).
---
## API REST
### Login escáner
```http
POST /api/auth/login
@@ -84,22 +368,35 @@ Content-Type: application/json
}
```
Respuesta exitosa:
Respuesta (Cognito):
```json
{
"success": true,
"accessToken": "eyJhbG...",
"refreshToken": "dXMtZWFzdC0x...",
"idToken": "eyJhbG...",
"tokenType": "Bearer",
"expiresIn": 28800,
"expiresIn": 3600,
"username": "scanner",
"roles": ["ROLE_SCANNER"]
}
```
Solo usuarios con rol `SCANNER` pueden iniciar sesion por API. El usuario `admin` no puede autenticarse por este endpoint.
### Renovar token
### Escanear codigo pase
```http
POST /api/auth/refresh
Content-Type: application/json
{
"refreshToken": "dXMtZWFzdC0x..."
}
```
Solo disponible con Cognito habilitado.
### Escanear código pase
```http
POST /api/pass-codes/scan
@@ -111,19 +408,48 @@ Content-Type: application/json
}
```
Marca el codigo como escaneado y cambia su estatus a `UTILIZADO`.
### Listado para el panel (requiere sesion admin)
### Listado JSON (panel admin, requiere sesión)
```http
GET /admin/pass-codes/list
```
Devuelve JSON con los codigos registrados. Se usa internamente para refrescar la tabla del panel.
---
## App Flutter (escáner)
Ubicación: `flutter-app/qrscanner/`
```mermaid
flowchart LR
Login["LoginScreen"] --> Home["HomeScreen"]
Home --> Scanner["ScannerScreen"]
Scanner -->|QR detectado| API["POST /api/pass-codes/scan"]
Login -->|credenciales| Auth["POST /api/auth/login"]
```
| Archivo | Rol |
|---------|-----|
| `lib/screens/login_screen.dart` | Login y sesión |
| `lib/screens/scanner_screen.dart` | Lectura QR + escaneo |
| `lib/services/auth_service.dart` | Login, SharedPreferences |
| `lib/services/pass_code_service.dart` | Scan con Bearer token |
| `lib/config/api_config.dart` | URL base de la API |
**Ejecutar apuntando al backend local:**
```bash
cd flutter-app/qrscanner
flutter run --dart-define=API_BASE_URL=http://TU_IP:8080
```
La app valida que el usuario tenga `ROLE_SCANNER` (o grupo `scanners`) antes de permitir el escaneo.
---
## Arranque local
Desde la raiz del proyecto:
### Opción recomendada
```bash
./run-local.sh
@@ -131,44 +457,39 @@ Desde la raiz del proyecto:
El script:
- Detecta automaticamente Temurin/Java 25
- Compila todos los modulos del proyecto
- Arranca el modulo ejecutable `base-admin-web-jar`
- Muestra el perfil activo y la base de datos en uso
- Detecta Java 25 (Temurin)
- Levanta Floci (y PostgreSQL si aplica)
- Espera y carga `cognito.local.env`
- Compila e inicia `base-admin-web-jar`
Si `SPRING_PROFILES_ACTIVE` incluye `postgres`, el script intenta levantar automaticamente el contenedor definido en `docker-compose-postgres.local.yml` antes de iniciar la aplicacion.
Si ya tienes `AWS_ENDPOINT_URL` exportado, no sobrescribe tus variables (útil para probar contra AWS).
Tambien puedes ejecutar:
### Maven manual (terminal)
```bash
mvn test
# 1. Levantar Floci (Cognito local)
docker compose -f docker-compose.local.yml up -d floci
# 2. Esperar el bootstrap (genera cognito.local.env)
# Revisa que exista: docker/floci/generated/cognito.local.env
# 3. Cargar variables Cognito en la shell
set -a && source docker/floci/generated/cognito.local.env && set +a
# 4. Compilar y arrancar
mvn -pl base-admin-web-jar -am -DskipTests install
mvn -pl base-admin-web-jar spring-boot:run
```
Aplicacion:
Si Floci ya estaba levantado de antes, basta con el `source` y el `spring-boot:run`.
- Login: `http://localhost:8080/login`
- Dashboard: `http://localhost:8080/dashboard`
- Codigos pase: `http://localhost:8080/admin/pass-codes`
- H2 Console: `http://localhost:8080/h2-console`
## Perfiles
- `dev`: H2 en archivo (`./data/h2/`), consola H2 habilitada, datos persistentes entre reinicios
- `prod`: cache Thymeleaf y H2 console deshabilitada
- `postgres`: datasource PostgreSQL, datos persistentes en el servidor o volumen Docker
- `test`: H2 en memoria con `create-drop`, usado solo por `mvn test`
Con `postgres`, `run-local.sh` intenta arrancar Docker Compose automaticamente.
Ejemplo con PostgreSQL local:
### PostgreSQL local
```bash
SPRING_PROFILES_ACTIVE=postgres ./run-local.sh
```
Ejemplo con PostgreSQL remoto:
### PostgreSQL remoto
```bash
SPRING_PROFILES_ACTIVE=postgres \
@@ -178,44 +499,230 @@ SPRING_DATASOURCE_PASSWORD=tu_password \
./run-local.sh
```
## Variables utiles
---
### Aplicacion
## Arranque manual e IntelliJ IDEA
- `SERVER_PORT`
- `SPRING_PROFILES_ACTIVE`
- `APP_TITLE`
Si abres el proyecto en IntelliJ (o cualquier IDE) sin usar `run-local.sh`, debes levantar Floci y configurar variables de entorno tú mismo. `run-local.sh` automatiza exactamente esos pasos.
```mermaid
flowchart TD
A["Abrir proyecto Maven en IntelliJ"] --> B["Java 25 como Project SDK"]
B --> C["docker compose up floci"]
C --> D["Esperar cognito.local.env"]
D --> E["Configurar Run Configuration"]
E --> F["Run BaseAdminWebApplication"]
F --> G["http://localhost:8080/login"]
```
### Requisitos previos
| Requisito | Versión / nota |
|-----------|----------------|
| JDK | **Java 25** (Temurin recomendado) |
| Maven | Incluido en el proyecto (wrapper no obligatorio; usa el Maven del IDE) |
| Docker | Para Cognito local (Floci) en puerto `4566` |
| IntelliJ | Ultimate o Community (con plugin Spring Boot en Community) |
### 1. Importar el proyecto
1. **File → Open** y selecciona la carpeta raíz del repo (donde está el `pom.xml` padre).
2. IntelliJ detecta el proyecto **Maven multi-módulo**. Espera a que termine la indexación e importación.
3. **File → Project Structure → Project**: SDK = **Java 25**, language level = **25**.
4. **File → Settings → Build → Build Tools → Maven → Runner**: JRE = **Project SDK (Java 25)**.
### 2. Levantar Floci (Cognito local)
En una terminal (puede ser la Terminal integrada de IntelliJ):
```bash
docker compose -f docker-compose.local.yml up -d floci
```
La primera vez Floci ejecuta el bootstrap y escribe:
```
docker/floci/generated/cognito.local.env
```
Comprueba que el archivo existe (puede tardar ~30120 s):
```bash
cat docker/floci/generated/cognito.local.env
```
Si no aparece, revisa los logs:
```bash
docker logs base-admin-web-floci
```
**PostgreSQL local:** si usas el perfil `postgres`, levanta también el servicio:
```bash
docker compose -f docker-compose.local.yml up -d postgres floci
```
### 3. Run Configuration en IntelliJ
Crea una configuración **Spring Boot** (o **Application**):
| Campo | Valor |
|-------|-------|
| **Name** | `BaseAdminWebApplication` |
| **Main class** | `mx.gob.slp.baseadminweb.jar.BaseAdminWebApplication` |
| **Module** | `base-admin-web-jar` |
| **Working directory** | `$PROJECT_DIR$/base-admin-web-jar` |
| **Active profiles** | `dev` (o `postgres` si aplica) |
> **Working directory:** H2 guarda datos en `./data/h2/` relativo al directorio de trabajo. Debe ser `base-admin-web-jar` para que coincida con `mvn spring-boot:run` y no mezclar rutas de BD.
#### Variables de entorno (Cognito local)
En **Environment variables**, pega el contenido de `docker/floci/generated/cognito.local.env`:
```
COGNITO_USER_POOL_ID=us-east-1_BaseAdminWeb
COGNITO_REGION=us-east-1
COGNITO_ISSUER_URI=http://localhost:4566/us-east-1_BaseAdminWeb
COGNITO_JWK_SET_URI=http://localhost:4566/us-east-1_BaseAdminWeb/.well-known/jwks.json
COGNITO_ADMIN_CLIENT_ID=<valor del archivo generado>
COGNITO_SCANNER_CLIENT_ID=<valor del archivo generado>
COGNITO_ADMIN_GROUP=admins
COGNITO_SCANNER_GROUP=scanners
AWS_ENDPOINT_URL=http://localhost:4566
AWS_DEFAULT_REGION=us-east-1
AWS_ACCESS_KEY_ID=test
AWS_SECRET_ACCESS_KEY=test
```
Los `CLIENT_ID` cambian en cada bootstrap; **copia siempre los del archivo generado**, no uses valores fijos de ejemplos antiguos.
**Alternativa:** plugin [EnvFile](https://plugins.jetbrains.com/plugin/7861-envfile) → añade `docker/floci/generated/cognito.local.env` como archivo de entorno en la Run Configuration.
#### Perfil `postgres` en IntelliJ
Además de **Active profiles** = `postgres`, añade en Environment variables:
```
SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/base_admin_web
SPRING_DATASOURCE_USERNAME=postgres
SPRING_DATASOURCE_PASSWORD=postgres
```
(Y las variables Cognito del paso anterior.)
### 4. Ejecutar
1. **Build → Build Project** (o `Ctrl+F9`).
2. Run ▶ en `BaseAdminWebApplication`.
3. Abre http://localhost:8080/login — usuario `admin` / `Admin123*`.
### 5. Desarrollo sin Docker (modo local puro)
Si no quieres Floci, desactiva Cognito y usa usuarios de la BD:
| Variable | Valor |
|----------|-------|
| `APP_COGNITO_ENABLED` | `false` |
No hace falta `AWS_ENDPOINT_URL` ni levantar Docker. Los usuarios `admin` y `scanner` se crean al arrancar vía `InitialDataLoader`.
El login API devuelve JWT local (HS256); **refresh no está disponible** en este modo.
### 6. Checklist de problemas frecuentes
| Síntoma | Causa probable | Solución |
|---------|----------------|----------|
| Login web falla con Cognito habilitado | Floci no está arriba o faltan env vars | `docker compose up -d floci` + cargar `cognito.local.env` |
| Login va a AWS real en local | Falta `AWS_ENDPOINT_URL` | Definir `AWS_ENDPOINT_URL=http://localhost:4566` |
| `Invalid client id` | Client IDs desactualizados | Regenerar con bootstrap y copiar de `cognito.local.env` |
| H2 vacío o ruta rara | Working directory incorrecto | `$PROJECT_DIR$/base-admin-web-jar` |
| Error de versión Java | SDK distinto a 25 | Project Structure → SDK Java 25 |
| Tests en IDE fallan por Cognito | Perfil `test` desactiva Cognito | Ejecutar tests con perfil `test` (ya configurado en `application-test.yml`) |
### 7. Comandos útiles desde IntelliJ
| Acción | Comando Maven (ventana Maven o terminal) |
|--------|------------------------------------------|
| Compilar todo | `mvn -pl base-admin-web-jar -am -DskipTests install` |
| Tests | `mvn test` |
| Solo módulo jar | clic derecho en `base-admin-web-jar` → Run Maven → `spring-boot:run` (tras exportar env vars en la shell) |
---
## Perfiles Spring
| Perfil | Base de datos | Cognito | Uso |
|--------|---------------|---------|-----|
| `dev` (default) | H2 en archivo `./data/h2/` | Floci (manual o `run-local.sh`) | Desarrollo diario |
| `postgres` | PostgreSQL | Floci | Desarrollo con BD real |
| `prod` | Configurable | AWS | Producción |
| `test` | H2 en memoria `create-drop` | Deshabilitado | `mvn test` |
---
## Variables de entorno
### Aplicación
| Variable | Default | Descripción |
|----------|---------|-------------|
| `SERVER_PORT` | `8080` | Puerto HTTP |
| `SPRING_PROFILES_ACTIVE` | `dev` | Perfil Spring |
| `APP_TITLE` | `Base Admin Web` | Título en vistas |
### Cognito
| Variable | Local (Floci) | Producción (AWS) |
|----------|---------------|------------------|
| `APP_COGNITO_ENABLED` | `true` | `true` |
| `COGNITO_REGION` | `us-east-1` | tu región |
| `COGNITO_USER_POOL_ID` | `us-east-1_BaseAdminWeb` | `us-east-1_XXXXX` |
| `COGNITO_ISSUER_URI` | `http://localhost:4566/...` | `https://cognito-idp.{region}.amazonaws.com/{poolId}` |
| `COGNITO_JWK_SET_URI` | `http://localhost:4566/.../jwks.json` | `https://cognito-idp.{region}.amazonaws.com/{poolId}/.well-known/jwks.json` |
| `COGNITO_ADMIN_CLIENT_ID` | generado por Floci | client del pool |
| `COGNITO_SCANNER_CLIENT_ID` | generado por Floci | client del pool |
| `COGNITO_ADMIN_GROUP` | `admins` | `admins` |
| `COGNITO_SCANNER_GROUP` | `scanners` | `scanners` |
| `AWS_ENDPOINT_URL` | `http://localhost:4566` | **no definir** |
### Base de datos
- `SPRING_DATASOURCE_URL`
- `SPRING_DATASOURCE_USERNAME`
- `SPRING_DATASOURCE_PASSWORD`
- `SPRING_JPA_HIBERNATE_DDL_AUTO`
| Variable | Descripción |
|----------|-------------|
| `SPRING_DATASOURCE_URL` | JDBC URL |
| `SPRING_DATASOURCE_USERNAME` | Usuario BD |
| `SPRING_DATASOURCE_PASSWORD` | Contraseña BD |
| `SPRING_JPA_HIBERNATE_DDL_AUTO` | `update` por defecto |
### JWT API
### JWT local (solo `APP_COGNITO_ENABLED=false`)
- `APP_JWT_SECRET` (minimo 32 caracteres)
- `APP_JWT_EXPIRATION_HOURS`
| Variable | Descripción |
|----------|-------------|
| `APP_JWT_SECRET` | Secreto HS256 (mín. 32 caracteres) |
| `APP_JWT_EXPIRATION_HOURS` | Expiración del token |
## Seguridad
### Usuarios semilla (modo local sin Cognito)
- Panel web: autenticacion por formulario con usuarios en base de datos
- API de escaneo: autenticacion stateless con JWT
- `/admin/**` y `/dashboard`: requieren rol `ADMIN`
- `/api/pass-codes/scan`: requiere rol `SCANNER` y token Bearer
- `/api/auth/login`: publico, pero solo emite token a usuarios `SCANNER`
| Variable | Default |
|----------|---------|
| `APP_ADMIN_USERNAME` / `APP_ADMIN_PASSWORD` | `admin` / `Admin123*` |
| `APP_SCANNER_USERNAME` / `APP_SCANNER_PASSWORD` | `scanner` / `Scanner123*` |
---
## Objetivo de esta base
Esta base deja resuelto lo minimo para arrancar nuevos proyectos internos:
Esta base deja resuelto lo mínimo para arrancar nuevos proyectos internos:
- autenticacion local funcional para administradores
- autenticacion API para escaneo movil o externo
- layout inicial con Metronic
- dashboard simple
- modulo de codigos pase con QR y API de escaneo
- estructura modular limpia
- soporte rapido para H2 y PostgreSQL
- Autenticación Cognito (local y AWS) con fallback a BD local
- Panel web con Metronic y sesión HTTP para administradores
- API stateless con JWT para escaneo móvil o externo
- App Flutter de referencia para escaneo QR
- dulo de códigos pase con alta, listado, QR y API de escaneo
- Estructura modular Maven limpia
- Soporte rápido para H2 y PostgreSQL
- Floci para desarrollo sin depender de AWS
El dashboard actual se mantiene deliberadamente sencillo para servir como punto de partida para futuras integraciones.
El dashboard se mantiene deliberadamente sencillo como punto de partida para futuras integraciones.
+4
View File
@@ -27,6 +27,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>cognitoidentityprovider</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
@@ -1,77 +1,50 @@
package mx.gob.slp.baseadminweb.configuration.security;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jwt.JwsHeader;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.List;
@Service
public class ApiAuthService {
private static final String SCANNER_ROLE = "ROLE_SCANNER";
private final ObjectProvider<CognitoAuthService> cognitoAuthService;
private final ObjectProvider<LocalJwtAuthService> localJwtAuthService;
private final AuthenticationManager authenticationManager;
private final JwtEncoder jwtEncoder;
private final JwtProperties jwtProperties;
public ApiAuthService(AuthenticationManager authenticationManager,
JwtEncoder jwtEncoder,
JwtProperties jwtProperties) {
this.authenticationManager = authenticationManager;
this.jwtEncoder = jwtEncoder;
this.jwtProperties = jwtProperties;
public ApiAuthService(ObjectProvider<CognitoAuthService> cognitoAuthService,
ObjectProvider<LocalJwtAuthService> localJwtAuthService) {
this.cognitoAuthService = cognitoAuthService;
this.localJwtAuthService = localJwtAuthService;
}
public ApiLoginResponse login(String username, String password) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password));
List<String> roles = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.toList();
if (roles.stream().noneMatch(SCANNER_ROLE::equals)) {
throw new BadCredentialsException("El usuario no tiene permisos para escanear codigos.");
CognitoAuthService cognito = cognitoAuthService.getIfAvailable();
if (cognito != null) {
return cognito.login(username, password);
}
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plusSeconds(jwtProperties.getExpirationHours() * 3600L);
LocalJwtAuthService local = localJwtAuthService.getIfAvailable();
if (local != null) {
return local.login(username, password);
}
JwtClaimsSet claims = JwtClaimsSet.builder()
.subject(authentication.getName())
.issuedAt(issuedAt)
.expiresAt(expiresAt)
.claim("roles", roles)
.build();
JwsHeader header = JwsHeader.with(MacAlgorithm.HS256).build();
String accessToken = jwtEncoder.encode(JwtEncoderParameters.from(header, claims)).getTokenValue();
return new ApiLoginResponse(
accessToken,
"Bearer",
jwtProperties.getExpirationHours() * 3600L,
authentication.getName(),
roles
);
throw new IllegalStateException("No hay proveedor de autenticacion API configurado.");
}
public record ApiLoginResponse(
String accessToken,
String tokenType,
long expiresIn,
String username,
List<String> roles
) {
public ApiLoginResponse refresh(String refreshToken) {
if (refreshToken == null || refreshToken.isBlank()) {
throw new BadCredentialsException("El refresh token es obligatorio.");
}
CognitoAuthService cognito = cognitoAuthService.getIfAvailable();
if (cognito != null) {
return cognito.refresh(refreshToken);
}
LocalJwtAuthService local = localJwtAuthService.getIfAvailable();
if (local != null) {
return local.refresh(refreshToken);
}
throw new IllegalStateException("No hay proveedor de autenticacion API configurado.");
}
}
@@ -0,0 +1,14 @@
package mx.gob.slp.baseadminweb.configuration.security;
import java.util.List;
public record ApiLoginResponse(
String accessToken,
String refreshToken,
String idToken,
String tokenType,
long expiresIn,
String username,
List<String> roles
) {
}
@@ -27,7 +27,7 @@ public class ApiSecurityConfig {
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/api/auth/login").permitAll()
.requestMatchers("/api/auth/login", "/api/auth/refresh").permitAll()
.requestMatchers("/api/pass-codes/scan").hasRole("SCANNER")
.anyRequest().authenticated()
)
@@ -0,0 +1,6 @@
package mx.gob.slp.baseadminweb.configuration.security;
import java.io.Serializable;
public record AuthenticatedWebUser(String username, String displayName) implements Serializable {
}
@@ -0,0 +1,52 @@
package mx.gob.slp.baseadminweb.configuration.security;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
@Service
@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "true")
public class CognitoAuthService {
private static final String SCANNER_ROLE = "ROLE_SCANNER";
private final CognitoUserAuthenticator cognitoUserAuthenticator;
private final CognitoProperties cognitoProperties;
public CognitoAuthService(CognitoUserAuthenticator cognitoUserAuthenticator,
CognitoProperties cognitoProperties) {
this.cognitoUserAuthenticator = cognitoUserAuthenticator;
this.cognitoProperties = cognitoProperties;
}
public ApiLoginResponse login(String username, String password) {
CognitoUserAuthenticator.AuthenticationResult result = cognitoUserAuthenticator.authenticate(
username,
password,
cognitoProperties.getScannerClientId(),
SCANNER_ROLE
);
return toLoginResponse(result);
}
public ApiLoginResponse refresh(String refreshToken) {
CognitoUserAuthenticator.AuthenticationResult result = cognitoUserAuthenticator.refresh(
refreshToken,
cognitoProperties.getScannerClientId(),
SCANNER_ROLE
);
return toLoginResponse(result);
}
private ApiLoginResponse toLoginResponse(CognitoUserAuthenticator.AuthenticationResult result) {
var tokens = result.tokens();
return new ApiLoginResponse(
tokens.accessToken(),
result.refreshToken(),
tokens.idToken(),
tokens.tokenType() != null ? tokens.tokenType() : "Bearer",
tokens.expiresIn() != null ? tokens.expiresIn() : 0L,
result.username(),
result.roles()
);
}
}
@@ -0,0 +1,33 @@
package mx.gob.slp.baseadminweb.configuration.security;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient;
import java.net.URI;
@Configuration
@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "true")
public class CognitoClientConfig {
@Bean(destroyMethod = "close")
CognitoIdentityProviderClient cognitoIdentityProviderClient(CognitoProperties properties) {
var builder = CognitoIdentityProviderClient.builder()
.region(Region.of(properties.getRegion()));
if (properties.usesLocalEndpoint()) {
builder.endpointOverride(URI.create(properties.getEndpoint()))
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create("test", "test")));
} else {
builder.credentialsProvider(DefaultCredentialsProvider.create());
}
return builder.build();
}
}
@@ -0,0 +1,31 @@
package mx.gob.slp.baseadminweb.configuration.security;
import com.nimbusds.jwt.JWTParser;
final class CognitoDisplayNameResolver {
private CognitoDisplayNameResolver() {
}
static String resolve(String idToken, String accessToken, String fallbackUsername) {
String displayName = extractNameClaim(idToken);
if (displayName == null || displayName.isBlank()) {
displayName = extractNameClaim(accessToken);
}
if (displayName == null || displayName.isBlank()) {
return fallbackUsername;
}
return displayName;
}
private static String extractNameClaim(String token) {
if (token == null || token.isBlank()) {
return null;
}
try {
return JWTParser.parse(token).getJWTClaimsSet().getStringClaim("name");
} catch (Exception ex) {
return null;
}
}
}
@@ -0,0 +1,102 @@
package mx.gob.slp.baseadminweb.configuration.security;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "app.cognito")
public class CognitoProperties {
private boolean enabled;
private String region = "us-east-1";
private String userPoolId = "us-east-1_BaseAdminWeb";
private String issuerUri = "http://localhost:4566/us-east-1_BaseAdminWeb";
private String jwkSetUri = "http://localhost:4566/us-east-1_BaseAdminWeb/.well-known/jwks.json";
private String endpoint;
private String scannerClientId;
private String adminClientId;
private String scannerGroup = "scanners";
private String adminGroup = "admins";
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
public String getUserPoolId() {
return userPoolId;
}
public void setUserPoolId(String userPoolId) {
this.userPoolId = userPoolId;
}
public String getIssuerUri() {
return issuerUri;
}
public void setIssuerUri(String issuerUri) {
this.issuerUri = issuerUri;
}
public String getJwkSetUri() {
return jwkSetUri;
}
public void setJwkSetUri(String jwkSetUri) {
this.jwkSetUri = jwkSetUri;
}
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public boolean usesLocalEndpoint() {
return endpoint != null && !endpoint.isBlank();
}
public String getScannerClientId() {
return scannerClientId;
}
public void setScannerClientId(String scannerClientId) {
this.scannerClientId = scannerClientId;
}
public String getAdminClientId() {
return adminClientId;
}
public void setAdminClientId(String adminClientId) {
this.adminClientId = adminClientId;
}
public String getScannerGroup() {
return scannerGroup;
}
public void setScannerGroup(String scannerGroup) {
this.scannerGroup = scannerGroup;
}
public String getAdminGroup() {
return adminGroup;
}
public void setAdminGroup(String adminGroup) {
this.adminGroup = adminGroup;
}
}
@@ -0,0 +1,29 @@
package mx.gob.slp.baseadminweb.configuration.security;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
final class CognitoRoleMapper {
private CognitoRoleMapper() {
}
static List<String> toRoles(Collection<String> groups, CognitoProperties properties) {
if (groups == null || groups.isEmpty()) {
return List.of();
}
List<String> roles = new ArrayList<>();
for (String group : groups) {
if (properties.getScannerGroup().equalsIgnoreCase(group)) {
roles.add("ROLE_SCANNER");
} else if (properties.getAdminGroup().equalsIgnoreCase(group)) {
roles.add("ROLE_ADMIN");
} else {
roles.add("ROLE_" + group.toUpperCase());
}
}
return roles;
}
}
@@ -0,0 +1,119 @@
package mx.gob.slp.baseadminweb.configuration.security;
import com.nimbusds.jwt.JWTParser;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.stereotype.Component;
import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient;
import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthFlowType;
import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthenticationResultType;
import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthRequest;
import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthResponse;
import software.amazon.awssdk.services.cognitoidentityprovider.model.NotAuthorizedException;
import software.amazon.awssdk.services.cognitoidentityprovider.model.UserNotFoundException;
import java.text.ParseException;
import java.util.List;
import java.util.Map;
@Component
@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "true")
public class CognitoUserAuthenticator {
private final CognitoIdentityProviderClient cognitoClient;
private final CognitoProperties cognitoProperties;
public CognitoUserAuthenticator(CognitoIdentityProviderClient cognitoClient,
CognitoProperties cognitoProperties) {
this.cognitoClient = cognitoClient;
this.cognitoProperties = cognitoProperties;
}
public AuthenticationResult authenticate(String username,
String password,
String clientId,
String requiredRole) {
try {
InitiateAuthResponse response = cognitoClient.initiateAuth(InitiateAuthRequest.builder()
.authFlow(AuthFlowType.USER_PASSWORD_AUTH)
.clientId(clientId)
.authParameters(Map.of(
"USERNAME", username,
"PASSWORD", password
))
.build());
return toAuthenticationResult(response.authenticationResult(), username, requiredRole, null);
} catch (NotAuthorizedException | UserNotFoundException ex) {
throw new BadCredentialsException("Credenciales invalidas.");
}
}
public AuthenticationResult refresh(String refreshToken, String clientId, String requiredRole) {
try {
InitiateAuthResponse response = cognitoClient.initiateAuth(InitiateAuthRequest.builder()
.authFlow(AuthFlowType.REFRESH_TOKEN_AUTH)
.clientId(clientId)
.authParameters(Map.of("REFRESH_TOKEN", refreshToken))
.build());
return toAuthenticationResult(response.authenticationResult(), null, requiredRole, refreshToken);
} catch (NotAuthorizedException ex) {
throw new BadCredentialsException("Refresh token invalido o expirado.");
}
}
private AuthenticationResult toAuthenticationResult(AuthenticationResultType authResult,
String fallbackUsername,
String requiredRole,
String fallbackRefreshToken) {
if (authResult == null || authResult.accessToken() == null) {
throw new BadCredentialsException("No se pudo autenticar con Cognito.");
}
List<String> groups = extractGroups(authResult.accessToken());
List<String> roles = CognitoRoleMapper.toRoles(groups, cognitoProperties);
if (roles.stream().noneMatch(requiredRole::equals)) {
throw new BadCredentialsException("El usuario no tiene permisos para acceder.");
}
String username = extractUsername(authResult.accessToken(), fallbackUsername);
String refreshToken = authResult.refreshToken() != null
? authResult.refreshToken()
: fallbackRefreshToken;
return new AuthenticationResult(authResult, username, roles, groups, refreshToken);
}
private String extractUsername(String accessToken, String fallback) {
try {
var claims = JWTParser.parse(accessToken).getJWTClaimsSet();
String username = claims.getStringClaim("username");
if (username == null || username.isBlank()) {
username = claims.getStringClaim("cognito:username");
}
return username != null && !username.isBlank() ? username : fallback;
} catch (ParseException ex) {
throw new BadCredentialsException("No se pudo leer el usuario del token.");
}
}
private List<String> extractGroups(String accessToken) {
try {
var claims = JWTParser.parse(accessToken).getJWTClaimsSet();
List<String> groups = claims.getStringListClaim("cognito:groups");
return groups != null ? groups : List.of();
} catch (ParseException ex) {
throw new BadCredentialsException("No se pudieron leer los grupos del token.");
}
}
public record AuthenticationResult(
AuthenticationResultType tokens,
String username,
List<String> roles,
List<String> groups,
String refreshToken
) {
}
}
@@ -0,0 +1,63 @@
package mx.gob.slp.baseadminweb.configuration.security;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;
@Component
@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "true")
public class CognitoWebAuthenticationProvider implements AuthenticationProvider {
private static final String ADMIN_ROLE = "ROLE_ADMIN";
private final CognitoUserAuthenticator cognitoUserAuthenticator;
private final CognitoProperties cognitoProperties;
public CognitoWebAuthenticationProvider(CognitoUserAuthenticator cognitoUserAuthenticator,
CognitoProperties cognitoProperties) {
this.cognitoUserAuthenticator = cognitoUserAuthenticator;
this.cognitoProperties = cognitoProperties;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
Object credentials = authentication.getCredentials();
if (credentials == null) {
throw new BadCredentialsException("La contrasena es obligatoria.");
}
CognitoUserAuthenticator.AuthenticationResult result = cognitoUserAuthenticator.authenticate(
username,
credentials.toString(),
cognitoProperties.getAdminClientId(),
ADMIN_ROLE
);
var authorities = result.roles().stream()
.map(SimpleGrantedAuthority::new)
.toList();
String displayName = CognitoDisplayNameResolver.resolve(
result.tokens().idToken(),
result.tokens().accessToken(),
result.username()
);
return UsernamePasswordAuthenticationToken.authenticated(
new AuthenticatedWebUser(result.username(), displayName),
null,
authorities
);
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
@@ -0,0 +1,17 @@
package mx.gob.slp.baseadminweb.configuration.security;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
@Configuration
@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "true")
public class CognitoWebSecurityConfig {
@Bean(name = "webAuthenticationManager")
AuthenticationManager webAuthenticationManager(CognitoWebAuthenticationProvider cognitoWebAuthenticationProvider) {
return new ProviderManager(cognitoWebAuthenticationProvider);
}
}
@@ -1,9 +1,13 @@
package mx.gob.slp.baseadminweb.configuration.security;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
@@ -19,12 +23,16 @@ import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Configuration
@EnableConfigurationProperties(JwtProperties.class)
@EnableConfigurationProperties({JwtProperties.class, CognitoProperties.class})
public class JwtConfig {
@Bean
@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "false")
JwtEncoder jwtEncoder(JwtProperties jwtProperties) {
OctetSequenceKey jwk = new OctetSequenceKey.Builder(jwtProperties.getSecret().getBytes(StandardCharsets.UTF_8))
.algorithm(JWSAlgorithm.HS256)
@@ -33,20 +41,35 @@ public class JwtConfig {
}
@Bean
JwtDecoder jwtDecoder(JwtProperties jwtProperties) {
@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "false")
JwtDecoder localJwtDecoder(JwtProperties jwtProperties) {
return NimbusJwtDecoder.withSecretKey(secretKey(jwtProperties))
.macAlgorithm(MacAlgorithm.HS256)
.build();
}
@Bean
JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter();
authoritiesConverter.setAuthoritiesClaimName("roles");
authoritiesConverter.setAuthorityPrefix("");
@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "true")
JwtDecoder cognitoJwtDecoder(CognitoProperties cognitoProperties) {
return NimbusJwtDecoder.withJwkSetUri(cognitoProperties.getJwkSetUri()).build();
}
@Bean
JwtAuthenticationConverter jwtAuthenticationConverter(CognitoProperties cognitoProperties) {
JwtGrantedAuthoritiesConverter localRolesConverter = new JwtGrantedAuthoritiesConverter();
localRolesConverter.setAuthoritiesClaimName("roles");
localRolesConverter.setAuthorityPrefix("");
JwtAuthenticationConverter authenticationConverter = new JwtAuthenticationConverter();
authenticationConverter.setJwtGrantedAuthoritiesConverter(authoritiesConverter);
authenticationConverter.setJwtGrantedAuthoritiesConverter(jwt -> {
Collection<GrantedAuthority> authorities = new ArrayList<>();
List<String> groups = jwt.getClaimAsStringList("cognito:groups");
CognitoRoleMapper.toRoles(groups, cognitoProperties).stream()
.map(SimpleGrantedAuthority::new)
.forEach(authorities::add);
authorities.addAll(localRolesConverter.convert(jwt));
return authorities;
});
return authenticationConverter;
}
@@ -0,0 +1,76 @@
package mx.gob.slp.baseadminweb.configuration.security;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jwt.JwsHeader;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.List;
@Service
@ConditionalOnProperty(prefix = "app.cognito", name = "enabled", havingValue = "false")
public class LocalJwtAuthService {
private static final String SCANNER_ROLE = "ROLE_SCANNER";
private final AuthenticationManager authenticationManager;
private final JwtEncoder jwtEncoder;
private final JwtProperties jwtProperties;
public LocalJwtAuthService(AuthenticationManager authenticationManager,
JwtEncoder jwtEncoder,
JwtProperties jwtProperties) {
this.authenticationManager = authenticationManager;
this.jwtEncoder = jwtEncoder;
this.jwtProperties = jwtProperties;
}
public ApiLoginResponse login(String username, String password) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password));
List<String> roles = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.toList();
if (roles.stream().noneMatch(SCANNER_ROLE::equals)) {
throw new BadCredentialsException("El usuario no tiene permisos para escanear codigos.");
}
Instant issuedAt = Instant.now();
Instant expiresAt = issuedAt.plusSeconds(jwtProperties.getExpirationHours() * 3600L);
JwtClaimsSet claims = JwtClaimsSet.builder()
.subject(authentication.getName())
.issuedAt(issuedAt)
.expiresAt(expiresAt)
.claim("roles", roles)
.build();
JwsHeader header = JwsHeader.with(MacAlgorithm.HS256).build();
String accessToken = jwtEncoder.encode(JwtEncoderParameters.from(header, claims)).getTokenValue();
return new ApiLoginResponse(
accessToken,
null,
null,
"Bearer",
jwtProperties.getExpirationHours() * 3600L,
authentication.getName(),
roles
);
}
public ApiLoginResponse refresh(String refreshToken) {
throw new BadCredentialsException("Refresh token no disponible sin Cognito.");
}
}
@@ -1,10 +1,13 @@
package mx.gob.slp.baseadminweb.configuration.security;
import mx.gob.slp.baseadminweb.application.service.AppUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
@@ -22,12 +25,17 @@ public class SecurityConfig {
private final AppUserDetailsService appUserDetailsService;
@Autowired(required = false)
@Qualifier("webAuthenticationManager")
private AuthenticationManager webAuthenticationManager;
public SecurityConfig(AppUserDetailsService appUserDetailsService) {
this.appUserDetailsService = appUserDetailsService;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
public SecurityFilterChain securityFilterChain(HttpSecurity http,
PasswordEncoder passwordEncoder) throws Exception {
http
.securityMatcher(request -> !request.getRequestURI().startsWith("/api/"))
.authorizeHttpRequests(authorize -> authorize
@@ -49,9 +57,16 @@ public class SecurityConfig {
.deleteCookies("JSESSIONID")
)
.headers(headers -> headers.frameOptions(frame -> frame.sameOrigin()))
.userDetailsService(appUserDetailsService)
.rememberMe(Customizer.withDefaults());
if (webAuthenticationManager != null) {
http.authenticationManager(webAuthenticationManager);
} else {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(appUserDetailsService);
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
http.authenticationProvider(daoAuthenticationProvider);
}
http.csrf(csrf -> csrf.ignoringRequestMatchers("/h2-console/**"));
return http.build();
}
@@ -65,4 +80,4 @@ public class SecurityConfig {
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
}
Binary file not shown.
@@ -1,3 +1,7 @@
app:
cognito:
enabled: false
spring:
datasource:
url: jdbc:h2:mem:baseadminweb-test;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
@@ -35,6 +35,17 @@ logging:
app:
title: ${APP_TITLE:Base Admin Web}
cognito:
enabled: ${APP_COGNITO_ENABLED:true}
region: ${COGNITO_REGION:us-east-1}
user-pool-id: ${COGNITO_USER_POOL_ID:us-east-1_BaseAdminWeb}
issuer-uri: ${COGNITO_ISSUER_URI:http://localhost:4566/us-east-1_BaseAdminWeb}
jwk-set-uri: ${COGNITO_JWK_SET_URI:http://localhost:4566/us-east-1_BaseAdminWeb/.well-known/jwks.json}
endpoint: ${AWS_ENDPOINT_URL:}
scanner-client-id: ${COGNITO_SCANNER_CLIENT_ID:4f878f50de4847b7adc7c0632a}
admin-client-id: ${COGNITO_ADMIN_CLIENT_ID:dde208d5b768465db6a2d8c6d2}
scanner-group: ${COGNITO_SCANNER_GROUP:scanners}
admin-group: ${COGNITO_ADMIN_GROUP:admins}
jwt:
secret: ${APP_JWT_SECRET:base-admin-web-dev-secret-change-me-32chars!!}
expiration-hours: ${APP_JWT_EXPIRATION_HOURS:8}
@@ -1,6 +1,7 @@
package mx.gob.slp.baseadminweb.web.controller;
import mx.gob.slp.baseadminweb.configuration.security.ApiAuthService;
import mx.gob.slp.baseadminweb.configuration.security.ApiLoginResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.BadCredentialsException;
@@ -9,6 +10,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.LinkedHashMap;
import java.util.Map;
@RestController
@@ -24,23 +26,46 @@ public class AuthApiController {
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
try {
ApiAuthService.ApiLoginResponse response = apiAuthService.login(request.username(), request.password());
return ResponseEntity.ok(Map.of(
"success", true,
"accessToken", response.accessToken(),
"tokenType", response.tokenType(),
"expiresIn", response.expiresIn(),
"username", response.username(),
"roles", response.roles()
));
ApiLoginResponse response = apiAuthService.login(request.username(), request.password());
return ResponseEntity.ok(toAuthBody(response));
} catch (BadCredentialsException ex) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(
"success", false,
"message", ex.getMessage()
));
return unauthorized(ex.getMessage());
}
}
@PostMapping("/refresh")
public ResponseEntity<?> refresh(@RequestBody RefreshRequest request) {
try {
ApiLoginResponse response = apiAuthService.refresh(request.refreshToken());
return ResponseEntity.ok(toAuthBody(response));
} catch (BadCredentialsException ex) {
return unauthorized(ex.getMessage());
}
}
private Map<String, Object> toAuthBody(ApiLoginResponse response) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("success", true);
body.put("accessToken", response.accessToken());
body.put("refreshToken", response.refreshToken());
body.put("idToken", response.idToken());
body.put("tokenType", response.tokenType());
body.put("expiresIn", response.expiresIn());
body.put("username", response.username());
body.put("roles", response.roles());
return body;
}
private ResponseEntity<Map<String, Object>> unauthorized(String message) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(Map.of(
"success", false,
"message", message
));
}
public record LoginRequest(String username, String password) {
}
public record RefreshRequest(String refreshToken) {
}
}
@@ -1,7 +1,7 @@
package mx.gob.slp.baseadminweb.web.controller;
import mx.gob.slp.baseadminweb.domain.model.AppUser;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import mx.gob.slp.baseadminweb.web.support.WebUserView;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@@ -15,8 +15,8 @@ public class DashboardController {
}
@GetMapping("/dashboard")
public String dashboard(@AuthenticationPrincipal AppUser user, Model model) {
model.addAttribute("user", user);
public String dashboard(Authentication authentication, Model model) {
model.addAttribute("user", WebUserView.from(authentication));
return "dashboard";
}
@@ -24,4 +24,4 @@ public class DashboardController {
public String adminHome() {
return "redirect:/dashboard";
}
}
}
@@ -1,9 +1,9 @@
package mx.gob.slp.baseadminweb.web.controller;
import mx.gob.slp.baseadminweb.application.service.PassCodeService;
import mx.gob.slp.baseadminweb.domain.model.AppUser;
import mx.gob.slp.baseadminweb.web.dto.PassCodeListItem;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import mx.gob.slp.baseadminweb.web.support.WebUserView;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@@ -26,8 +26,8 @@ public class PassCodeController {
}
@GetMapping
public String list(@AuthenticationPrincipal AppUser user, Model model) {
model.addAttribute("user", user);
public String list(Authentication authentication, Model model) {
model.addAttribute("user", WebUserView.from(authentication));
model.addAttribute("passCodes", passCodeService.findAll());
return "pass-codes";
}
@@ -0,0 +1,25 @@
package mx.gob.slp.baseadminweb.web.support;
import mx.gob.slp.baseadminweb.configuration.security.AuthenticatedWebUser;
import mx.gob.slp.baseadminweb.domain.model.AppUser;
import org.springframework.security.core.Authentication;
public record WebUserView(String username, String displayName) {
public static WebUserView from(Authentication authentication) {
if (authentication == null) {
return new WebUserView("usuario", "Usuario");
}
Object principal = authentication.getPrincipal();
if (principal instanceof AppUser appUser) {
return new WebUserView(appUser.getUsername(), appUser.getDisplayName());
}
if (principal instanceof AuthenticatedWebUser webUser) {
return new WebUserView(webUser.username(), webUser.displayName());
}
String name = authentication.getName();
return new WebUserView(name, name);
}
}
@@ -45,9 +45,12 @@
</div>
<div class="d-flex justify-content-between text-gray-500 fs-7 mb-8">
<span>Usuario demo: <strong>admin</strong></span>
<span>Usuario: <strong>admin</strong></span>
<span>Password: <strong>Admin123*</strong></span>
</div>
<div class="text-gray-500 fs-8 mb-8 text-center">
Autenticacion via Cognito local (grupo <strong>admins</strong>)
</div>
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">
+3 -15
View File
@@ -1,15 +1,3 @@
services:
postgres:
image: postgres:16
container_name: base-admin-web-postgres
environment:
POSTGRES_DB: base_admin_web
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- "5432:5432"
volumes:
- base_admin_web_postgres_data:/var/lib/postgresql/data
volumes:
base_admin_web_postgres_data:
# Alias de compatibilidad. Usa docker-compose.local.yml como archivo principal.
include:
- path: docker-compose.local.yml
+45
View File
@@ -0,0 +1,45 @@
services:
postgres:
image: postgres:16
container_name: base-admin-web-postgres
environment:
POSTGRES_DB: base_admin_web
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- "5432:5432"
volumes:
- base_admin_web_postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d base_admin_web"]
interval: 5s
timeout: 5s
retries: 10
floci:
image: floci/floci:latest-compat
container_name: base-admin-web-floci
ports:
- "4566:4566"
volumes:
- base_admin_web_floci_data:/app/data
- ./docker/floci/init/ready.d:/etc/floci/init/ready.d:ro
- ./docker/floci/generated:/generated
environment:
FLOCI_HOSTNAME: floci
FLOCI_STORAGE_MODE: persistent
FLOCI_STORAGE_PERSISTENT_PATH: /app/data
FLOCI_DEFAULT_REGION: us-east-1
FLOCI_DEFAULT_ACCOUNT_ID: "000000000000"
FLOCI_INIT_HOOKS_TIMEOUT_SECONDS: "120"
COGNITO_USER_POOL_ID: us-east-1_BaseAdminWeb
COGNITO_ADMIN_USERNAME: admin
COGNITO_ADMIN_PASSWORD: Admin123*
COGNITO_SCANNER_USERNAME: scanner
COGNITO_SCANNER_PASSWORD: Scanner123*
COGNITO_ENV_OUTPUT_DIR: /generated
restart: unless-stopped
volumes:
base_admin_web_postgres_data:
base_admin_web_floci_data:
+15
View File
@@ -0,0 +1,15 @@
# Copia generada automaticamente en ./docker/floci/generated/cognito.local.env
# despues del primer arranque de Floci.
COGNITO_USER_POOL_ID=us-east-1_BaseAdminWeb
COGNITO_REGION=us-east-1
COGNITO_ISSUER_URI=http://localhost:4566/us-east-1_BaseAdminWeb
COGNITO_JWK_SET_URI=http://localhost:4566/us-east-1_BaseAdminWeb/.well-known/jwks.json
COGNITO_ADMIN_CLIENT_ID=REEMPLAZAR_CON_VALOR_GENERADO
COGNITO_SCANNER_CLIENT_ID=REEMPLAZAR_CON_VALOR_GENERADO
COGNITO_ADMIN_GROUP=admins
COGNITO_SCANNER_GROUP=scanners
AWS_ENDPOINT_URL=http://localhost:4566
AWS_DEFAULT_REGION=us-east-1
AWS_ACCESS_KEY_ID=test
AWS_SECRET_ACCESS_KEY=test
View File
+172
View File
@@ -0,0 +1,172 @@
#!/bin/sh
set -eu
POOL_ID="${COGNITO_USER_POOL_ID:-us-east-1_BaseAdminWeb}"
POOL_NAME="${COGNITO_USER_POOL_NAME:-base-admin-web}"
REGION="${AWS_DEFAULT_REGION:-us-east-1}"
DATA_DIR="${FLOCI_STORAGE_PERSISTENT_PATH:-/app/data}"
ENV_OUTPUT_DIR="${COGNITO_ENV_OUTPUT_DIR:-/generated}"
ENV_FILE="${ENV_OUTPUT_DIR}/cognito.local.env"
ADMIN_USERNAME="${COGNITO_ADMIN_USERNAME:-admin}"
ADMIN_PASSWORD="${COGNITO_ADMIN_PASSWORD:-Admin123*}"
ADMIN_NAME="${COGNITO_ADMIN_DISPLAY_NAME:-Administrador Base}"
SCANNER_USERNAME="${COGNITO_SCANNER_USERNAME:-scanner}"
SCANNER_PASSWORD="${COGNITO_SCANNER_PASSWORD:-Scanner123*}"
SCANNER_NAME="${COGNITO_SCANNER_DISPLAY_NAME:-Escaneador}"
ADMIN_CLIENT_NAME="${COGNITO_ADMIN_CLIENT_NAME:-base-admin-web-admin}"
SCANNER_CLIENT_NAME="${COGNITO_SCANNER_CLIENT_NAME:-base-admin-web-scanner}"
ADMIN_GROUP="${COGNITO_ADMIN_GROUP:-admins}"
SCANNER_GROUP="${COGNITO_SCANNER_GROUP:-scanners}"
mkdir -p "$DATA_DIR" "$ENV_OUTPUT_DIR"
log() {
printf '[cognito-bootstrap] %s\n' "$1" >&2
}
pool_exists() {
aws cognito-idp describe-user-pool --user-pool-id "$POOL_ID" >/dev/null 2>&1
}
create_pool() {
log "Creando user pool $POOL_ID"
aws cognito-idp create-user-pool \
--pool-name "$POOL_NAME" \
--user-pool-tags "floci:override-id=${POOL_ID}" \
--policies "PasswordPolicy={MinimumLength=8,RequireUppercase=true,RequireLowercase=true,RequireNumbers=true,RequireSymbols=true}" \
--username-configuration "CaseSensitive=false" \
--mfa-configuration OFF \
--account-recovery-setting "RecoveryMechanisms=[{Priority=1,Name=verified_email}]"
}
find_client_id() {
client_name="$1"
aws cognito-idp list-user-pool-clients \
--user-pool-id "$POOL_ID" \
--max-results 60 \
--query "UserPoolClients[?ClientName=='${client_name}'].ClientId | [0]" \
--output text
}
create_client() {
client_name="$1"
existing_id="$(find_client_id "$client_name" || true)"
if [ -n "$existing_id" ] && [ "$existing_id" != "None" ]; then
log "App client $client_name ya existe ($existing_id)"
printf '%s' "$existing_id"
return 0
fi
log "Creando app client $client_name"
aws cognito-idp create-user-pool-client \
--user-pool-id "$POOL_ID" \
--client-name "$client_name" \
--explicit-auth-flows \
ALLOW_USER_PASSWORD_AUTH \
ALLOW_REFRESH_TOKEN_AUTH \
ALLOW_USER_SRP_AUTH \
--prevent-user-existence-errors ENABLED \
--supported-identity-providers COGNITO \
--query "UserPoolClient.ClientId" \
--output text
}
create_group_if_needed() {
group_name="$1"
description="$2"
if aws cognito-idp get-group --user-pool-id "$POOL_ID" --group-name "$group_name" >/dev/null 2>&1; then
log "Grupo $group_name ya existe"
return 0
fi
log "Creando grupo $group_name"
aws cognito-idp create-group \
--user-pool-id "$POOL_ID" \
--group-name "$group_name" \
--description "$description"
}
user_exists() {
username="$1"
aws cognito-idp admin-get-user \
--user-pool-id "$POOL_ID" \
--username "$username" >/dev/null 2>&1
}
create_user_if_needed() {
username="$1"
password="$2"
display_name="$3"
group_name="$4"
if ! user_exists "$username"; then
log "Creando usuario $username"
aws cognito-idp admin-create-user \
--user-pool-id "$POOL_ID" \
--username "$username" \
--message-action SUPPRESS \
--user-attributes "Name=name,Value=${display_name}"
else
log "Usuario $username ya existe"
fi
aws cognito-idp admin-set-user-password \
--user-pool-id "$POOL_ID" \
--username "$username" \
--password "$password" \
--permanent
aws cognito-idp admin-add-user-to-group \
--user-pool-id "$POOL_ID" \
--username "$username" \
--group-name "$group_name" >/dev/null 2>&1 || true
}
write_env_file() {
admin_client_id="$1"
scanner_client_id="$2"
cat > "$ENV_FILE" <<EOF
# Generado por docker/floci/init/ready.d/10-cognito-bootstrap.sh
COGNITO_USER_POOL_ID=${POOL_ID}
COGNITO_REGION=${REGION}
COGNITO_ISSUER_URI=http://localhost:4566/${POOL_ID}
COGNITO_JWK_SET_URI=http://localhost:4566/${POOL_ID}/.well-known/jwks.json
COGNITO_ADMIN_CLIENT_ID=${admin_client_id}
COGNITO_SCANNER_CLIENT_ID=${scanner_client_id}
COGNITO_ADMIN_GROUP=${ADMIN_GROUP}
COGNITO_SCANNER_GROUP=${SCANNER_GROUP}
AWS_ENDPOINT_URL=http://localhost:4566
AWS_DEFAULT_REGION=${REGION}
AWS_ACCESS_KEY_ID=test
AWS_SECRET_ACCESS_KEY=test
EOF
log "Variables locales guardadas en $ENV_FILE"
}
log "Iniciando bootstrap de Cognito"
if ! pool_exists; then
create_pool
else
log "User pool $POOL_ID ya existe"
fi
ADMIN_CLIENT_ID="$(create_client "$ADMIN_CLIENT_NAME")"
SCANNER_CLIENT_ID="$(create_client "$SCANNER_CLIENT_NAME")"
create_group_if_needed "$ADMIN_GROUP" "Administradores del panel web"
create_group_if_needed "$SCANNER_GROUP" "Usuarios escaner movil/API"
create_user_if_needed "$ADMIN_USERNAME" "$ADMIN_PASSWORD" "$ADMIN_NAME" "$ADMIN_GROUP"
create_user_if_needed "$SCANNER_USERNAME" "$SCANNER_PASSWORD" "$SCANNER_NAME" "$SCANNER_GROUP"
write_env_file "$ADMIN_CLIENT_ID" "$SCANNER_CLIENT_ID"
log "Bootstrap completado"
log "Admin: $ADMIN_USERNAME / $ADMIN_PASSWORD (grupo $ADMIN_GROUP)"
log "Scanner: $SCANNER_USERNAME / $SCANNER_PASSWORD (grupo $SCANNER_GROUP)"
-45
View File
@@ -1,45 +0,0 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
/coverage/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
-33
View File
@@ -1,33 +0,0 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "924134a44c189315be2148659913dda1671cbe99"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 924134a44c189315be2148659913dda1671cbe99
base_revision: 924134a44c189315be2148659913dda1671cbe99
- platform: android
create_revision: 924134a44c189315be2148659913dda1671cbe99
base_revision: 924134a44c189315be2148659913dda1671cbe99
- platform: ios
create_revision: 924134a44c189315be2148659913dda1671cbe99
base_revision: 924134a44c189315be2148659913dda1671cbe99
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'
-17
View File
@@ -1,17 +0,0 @@
# qrscanner
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter)
- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
@@ -1,28 +0,0 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
-14
View File
@@ -1,14 +0,0 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks
@@ -1,45 +0,0 @@
plugins {
id("com.android.application")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.example.qrscanner"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.qrscanner"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
kotlin {
compilerOptions {
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
}
}
flutter {
source = "../.."
}
@@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
@@ -1,50 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.VIBRATE" />
<application
android:label="qrscanner"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
@@ -1,5 +0,0 @@
package com.example.qrscanner
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>
@@ -1,7 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
@@ -1,24 +0,0 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}
@@ -1,6 +0,0 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
# This newDsl flag was added by the Flutter template
android.newDsl=false
# This builtInKotlin flag was added by the Flutter template
android.builtInKotlin=false
@@ -1,5 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip
@@ -1,26 +0,0 @@
pluginManagement {
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "9.0.1" apply false
id("org.jetbrains.kotlin.android") version "2.3.20" apply false
}
include(":app")
Binary file not shown.
Binary file not shown.
Binary file not shown.
-34
View File
@@ -1,34 +0,0 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
</dict>
</plist>
@@ -1 +0,0 @@
#include "Generated.xcconfig"
@@ -1 +0,0 @@
#include "Generated.xcconfig"
@@ -1,644 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; };
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
packageProductDependencies = (
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
);
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
packageReferences = (
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
);
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.qrscanner;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.qrscanner.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.qrscanner.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.qrscanner.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.qrscanner;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.qrscanner;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
isa = XCSwiftPackageProductDependency;
productName = FlutterGeneratedPluginSwiftPackage;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
@@ -1,119 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Prepare Flutter Framework Script"
scriptText = "/bin/sh &quot;$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh&quot; prepare&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
@@ -1,16 +0,0 @@
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
}
@@ -1,122 +0,0 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

@@ -1,23 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

@@ -1,5 +0,0 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>
@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
@@ -1,72 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Qrscanner</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>qrscanner</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>La app necesita la cámara para escanear códigos QR de pase.</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>flutter</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
@@ -1 +0,0 @@
#import "GeneratedPluginRegistrant.h"
@@ -1,6 +0,0 @@
import Flutter
import UIKit
class SceneDelegate: FlutterSceneDelegate {
}
@@ -1,12 +0,0 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}
@@ -1,16 +0,0 @@
/// URL base del API de base-admin-web.
///
/// Configúrala al ejecutar o compilar si necesitas otra IP:
/// `flutter run --dart-define=API_BASE_URL=http://otra-ip:8080`
class ApiConfig {
ApiConfig._();
static const String baseUrl = String.fromEnvironment(
'API_BASE_URL',
defaultValue: 'http://10.99.51.6:8080',
);
static String get loginUrl => '$baseUrl/api/auth/login';
static String get scanUrl => '$baseUrl/api/pass-codes/scan';
}
-21
View File
@@ -1,21 +0,0 @@
import 'package:flutter/material.dart';
import 'package:qrscanner/screens/login_screen.dart';
import 'package:qrscanner/theme/app_theme.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'QR Scanner',
debugShowCheckedModeBanner: false,
theme: AppTheme.darkTheme,
home: const LoginScreen(),
);
}
}
@@ -1,52 +0,0 @@
import 'package:qrscanner/models/login_response.dart';
class AuthSession {
const AuthSession({
required this.username,
required this.accessToken,
required this.tokenType,
required this.expiresIn,
required this.roles,
});
factory AuthSession.fromLoginResponse(LoginResponse response) {
return AuthSession(
username: response.username ?? '',
accessToken: response.accessToken ?? '',
tokenType: response.tokenType ?? 'Bearer',
expiresIn: response.expiresIn ?? 0,
roles: response.roles,
);
}
factory AuthSession.fromJson(Map<String, dynamic> json) {
return AuthSession(
username: json['username'] as String? ?? '',
accessToken: json['accessToken'] as String? ?? '',
tokenType: json['tokenType'] as String? ?? 'Bearer',
expiresIn: (json['expiresIn'] as num?)?.toInt() ?? 0,
roles: (json['roles'] as List<dynamic>?)
?.map((role) => role.toString())
.toList() ??
const [],
);
}
final String username;
final String accessToken;
final String tokenType;
final int expiresIn;
final List<String> roles;
String get authorizationHeader => '$tokenType $accessToken';
bool get isScanner => roles.contains('ROLE_SCANNER');
Map<String, dynamic> toJson() => {
'username': username,
'accessToken': accessToken,
'tokenType': tokenType,
'expiresIn': expiresIn,
'roles': roles,
};
}

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