Compare commits
3 Commits
6cfa321386
...
61586ef169
Author | SHA1 | Date | |
---|---|---|---|
61586ef169 | |||
4b5c74d824 | |||
0792862f25 |
@ -82,6 +82,8 @@ namespace API.Application.Users.Commands
|
|||||||
CreatedAt = DateTime.UtcNow.AddHours(2),
|
CreatedAt = DateTime.UtcNow.AddHours(2),
|
||||||
UpdatedAt = DateTime.UtcNow.AddHours(2),
|
UpdatedAt = DateTime.UtcNow.AddHours(2),
|
||||||
HashedPassword = hashedPassword,
|
HashedPassword = hashedPassword,
|
||||||
|
RefreshToken = System.Guid.NewGuid().ToString(),
|
||||||
|
RefreshTokenExpiresAt = DateTime.UtcNow.AddDays(7),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,10 @@ namespace API.Application.Users.Commands
|
|||||||
}
|
}
|
||||||
var jwtToken = _tokenHelper.GenerateJwtToken(user);
|
var jwtToken = _tokenHelper.GenerateJwtToken(user);
|
||||||
|
|
||||||
return new OkObjectResult(new { token = jwtToken, id = user.Id});
|
user.RefreshToken = System.Guid.NewGuid().ToString();
|
||||||
|
_repository.Save();
|
||||||
|
|
||||||
|
return new OkObjectResult(new { token = jwtToken, id = user.Id, refreshToken = user.RefreshToken });
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,15 +58,13 @@ namespace API.Controllers
|
|||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<ActionResult<List<UserDTO>>> GetUsers()
|
public async Task<ActionResult<List<UserDTO>>> GetUsers()
|
||||||
{
|
{
|
||||||
return await _queryAllUsers.Handle();
|
return await _queryAllUsers.Handle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
public async Task<ActionResult<UserDTO>> GetUser(string id)
|
public async Task<ActionResult<UserDTO>> GetUser(string id)
|
||||||
{
|
{
|
||||||
return await _queryUserById.Handle(id);
|
return await _queryUserById.Handle(id);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
[Authorize]
|
||||||
@ -89,14 +87,11 @@ namespace API.Controllers
|
|||||||
return await _deleteUser.Handle(id);
|
return await _deleteUser.Handle(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Authorize]
|
|
||||||
[HttpPost("/RefreshToken")]
|
[HttpPost("/RefreshToken")]
|
||||||
public async Task<IActionResult> RefreshToken()
|
public async Task<IActionResult> RefreshToken(RefreshTokenDTO refreshTokenDTO)
|
||||||
{
|
{
|
||||||
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
User user = await _repository.QueryUserByRefreshTokenAsync(refreshTokenDTO.RefreshToken);
|
||||||
var user = await _repository.QueryUserByIdAsync(userId);
|
|
||||||
return new OkObjectResult(_tokenHelper.GenerateJwtToken(user));
|
return new OkObjectResult(_tokenHelper.GenerateJwtToken(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,3 +39,8 @@ public class UpdateUserDTO
|
|||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class RefreshTokenDTO
|
||||||
|
{
|
||||||
|
public string RefreshToken { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -10,5 +10,7 @@ namespace API.Persistence.Repositories
|
|||||||
Task<User> QueryUserByIdAsync(string id);
|
Task<User> QueryUserByIdAsync(string id);
|
||||||
Task<User> QueryUserByEmailAsync(string email);
|
Task<User> QueryUserByEmailAsync(string email);
|
||||||
Task<bool> UpdateUserAsync(User user);
|
Task<bool> UpdateUserAsync(User user);
|
||||||
|
Task<User> QueryUserByRefreshTokenAsync(string refreshToken);
|
||||||
|
void Save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ namespace API.Persistence.Repositories
|
|||||||
public class UserRepository(AppDBContext context) : IUserRepository
|
public class UserRepository(AppDBContext context) : IUserRepository
|
||||||
{
|
{
|
||||||
private readonly AppDBContext _context = context;
|
private readonly AppDBContext _context = context;
|
||||||
|
|
||||||
public async Task<List<User>> QueryAllUsersAsync()
|
public async Task<List<User>> QueryAllUsersAsync()
|
||||||
{
|
{
|
||||||
return await _context.Users.ToListAsync();
|
return await _context.Users.ToListAsync();
|
||||||
@ -16,14 +17,12 @@ namespace API.Persistence.Repositories
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await _context.Users
|
return await _context.Users.FirstOrDefaultAsync(user => user.Id == id);
|
||||||
.FirstOrDefaultAsync(user => user.Id == id);
|
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
return new User();
|
return new User();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> CreateUserAsync(User user)
|
public async Task<string> CreateUserAsync(User user)
|
||||||
@ -73,7 +72,16 @@ namespace API.Persistence.Repositories
|
|||||||
public async Task<User> QueryUserByEmailAsync(string email)
|
public async Task<User> QueryUserByEmailAsync(string email)
|
||||||
{
|
{
|
||||||
return await _context.Users.SingleOrDefaultAsync(u => u.Email == email);
|
return await _context.Users.SingleOrDefaultAsync(u => u.Email == email);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<User> QueryUserByRefreshTokenAsync(string refreshToken)
|
||||||
|
{
|
||||||
|
return await _context.Users.SingleOrDefaultAsync(u => u.RefreshToken == refreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
_context.SaveChanges();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,8 @@ enum ApiService {
|
|||||||
app,
|
app,
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> request(BuildContext context, ApiService service, String method, String path, Object? body) async {
|
Future<String?> request(BuildContext? context, ApiService service, String method, String path, Object? body) async {
|
||||||
final messenger = ScaffoldMessenger.of(context);
|
final messenger = context != null ? ScaffoldMessenger.of(context) : null;
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
final host = switch (service) {
|
final host = switch (service) {
|
||||||
@ -42,17 +42,20 @@ Future<String?> request(BuildContext context, ApiService service, String method,
|
|||||||
body: body != null ? jsonEncode(body) : null,
|
body: body != null ? jsonEncode(body) : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (e) {
|
||||||
messenger.showSnackBar(const SnackBar(content: Text('Unable to connect to server')));
|
debugPrint(e.toString());
|
||||||
|
messenger?.showSnackBar(const SnackBar(content: Text('Unable to connect to server')));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||||
try {
|
try {
|
||||||
final json = jsonDecode(response.body);
|
final json = jsonDecode(response.body);
|
||||||
messenger.showSnackBar(SnackBar(content: Text(json['message'])));
|
messenger?.showSnackBar(SnackBar(content: Text(json['message'] ?? json['title'])));
|
||||||
} catch (_) {
|
debugPrint('API error: ' + json['message']);
|
||||||
messenger.showSnackBar(SnackBar(content: Text('Something went wrong (HTTP ${response.statusCode})')));
|
} catch (e) {
|
||||||
|
debugPrint(e.toString());
|
||||||
|
messenger?.showSnackBar(SnackBar(content: Text('Something went wrong (HTTP ${response.statusCode})')));
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -66,7 +69,7 @@ Future<bool> isLoggedIn(BuildContext context) async {
|
|||||||
|
|
||||||
final token = prefs.getString('token');
|
final token = prefs.getString('token');
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
prefs.remove('id');
|
logout();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,15 +81,25 @@ Future<bool> isLoggedIn(BuildContext context) async {
|
|||||||
|
|
||||||
if (payload['exp'] < DateTime.now().millisecondsSinceEpoch / 1000) {
|
if (payload['exp'] < DateTime.now().millisecondsSinceEpoch / 1000) {
|
||||||
messenger.showSnackBar(const SnackBar(content: Text('Token expired, please sign in again')));
|
messenger.showSnackBar(const SnackBar(content: Text('Token expired, please sign in again')));
|
||||||
prefs.remove('token');
|
|
||||||
|
logout();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
messenger.showSnackBar(const SnackBar(content: Text('Invalid token, please sign in again')));
|
messenger.showSnackBar(const SnackBar(content: Text('Invalid token, please sign in again')));
|
||||||
prefs.remove('token');
|
|
||||||
debugPrint(e.toString());
|
debugPrint(e.toString());
|
||||||
|
|
||||||
|
logout();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void logout() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
|
prefs.remove('token');
|
||||||
|
prefs.remove('refresh-token');
|
||||||
|
prefs.remove('id');
|
||||||
|
}
|
||||||
|
@ -25,10 +25,7 @@ class _SideMenuState extends State<SideMenu> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _logout() async {
|
void _logout() async {
|
||||||
final prefs = await SharedPreferences.getInstance();
|
api.logout();
|
||||||
|
|
||||||
prefs.remove('token');
|
|
||||||
prefs.remove('id');
|
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
user = null;
|
user = null;
|
||||||
|
@ -19,20 +19,21 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
final passwordInput = TextEditingController();
|
final passwordInput = TextEditingController();
|
||||||
|
|
||||||
Future<void> _login() async {
|
Future<void> _login() async {
|
||||||
final token = await api.request(context, api.ApiService.auth, 'POST', '/api/Users/login', {
|
final response = await api.request(context, api.ApiService.auth, 'POST', '/api/Users/login', {
|
||||||
'email': emailInput.text,
|
'email': emailInput.text,
|
||||||
'password': passwordInput.text,
|
'password': passwordInput.text,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (token == null) return;
|
if (response == null) return;
|
||||||
|
|
||||||
// Assuming token is a JSON string
|
// Assuming token is a JSON string
|
||||||
Map<String, dynamic> json = jsonDecode(token);
|
Map<String, dynamic> json = jsonDecode(response);
|
||||||
Login jsonUser = Login.fromJson(json);
|
Login jsonUser = Login.fromJson(json);
|
||||||
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
final prefs = await SharedPreferences.getInstance();
|
||||||
prefs.setString('token', jsonUser.token);
|
prefs.setString('token', jsonUser.token);
|
||||||
prefs.setString('id', jsonUser.id);
|
prefs.setString('id', jsonUser.id);
|
||||||
|
prefs.setString('refresh-token', jsonUser.refreshToken);
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Successfully logged in')));
|
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Successfully logged in')));
|
||||||
|
@ -6,6 +6,7 @@ import 'package:flutter_map/flutter_map.dart';
|
|||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
import 'package:mobile/favorites.dart';
|
import 'package:mobile/favorites.dart';
|
||||||
import 'package:mobile/register.dart';
|
import 'package:mobile/register.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'login.dart';
|
import 'login.dart';
|
||||||
import 'base/sidemenu.dart';
|
import 'base/sidemenu.dart';
|
||||||
import 'profile.dart';
|
import 'profile.dart';
|
||||||
@ -15,7 +16,14 @@ import 'api.dart' as api;
|
|||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
void main() {
|
void main() async {
|
||||||
|
// Refresh JWT on startup
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
if (prefs.getString("token") != null && prefs.getString("refresh-token") != null) {
|
||||||
|
final token = await api.request(null, api.ApiService.auth, "POST", "/RefreshToken", {'refreshToken': prefs.getString("refresh-token")});
|
||||||
|
if (token != null) prefs.setString("token", token);
|
||||||
|
}
|
||||||
|
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,13 +25,15 @@ class Favorite {
|
|||||||
class Login {
|
class Login {
|
||||||
String token;
|
String token;
|
||||||
String id;
|
String id;
|
||||||
|
String refreshToken;
|
||||||
|
|
||||||
Login(this.token, this.id);
|
Login(this.token, this.id, this.refreshToken);
|
||||||
|
|
||||||
factory Login.fromJson(Map<String, dynamic> json) {
|
factory Login.fromJson(Map<String, dynamic> json) {
|
||||||
return Login(
|
return Login(
|
||||||
json['token'],
|
json['token'],
|
||||||
json['id'],
|
json['id'],
|
||||||
|
json['refreshToken'],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user