amigoscode / full-stack-professional Goto Github PK
View Code? Open in Web Editor NEWHome Page: https://amigoscode.com/courses/full-stack-professional
Home Page: https://amigoscode.com/courses/full-stack-professional
Well, I'm not positive about issuing JWT to a user who has been just signed up regardless of error:
To my mind we need to get a token and only then submit a profile to our database
public ResponseEntity<?> registerCustomer(
@Valid @RequestBody CustomerDto customerDto
) {
var token = jwtUtil.issueToken(customerDto.email(), "ROLE_USER");
customerService.addCustomer(customerDto);
return ResponseEntity.ok()
.header(HttpHeaders.AUTHORIZATION, token)
.build();
}
then I suggest add a couple of extra lines into your application.yml file
jwt:
token:
expires:
minutes: 60
secret:
key: placewhateverkeyyoulikebutmakesureitislongenough_placewhateverkeyyoulikebutmakesureitislongenough_placewhateverkeyyoulikebutmakesureitislongenough_
and here is my jwtUtil.java
@Service
public class JwtUtil {
private final Environment env;
public JwtUtil(Environment env) {
this.env = env;
}
public String issueToken(String subject) {
return issueToken(subject, Map.of());
}
public String issueToken(String subject, String ...scopes) {
return issueToken(subject, Map.of("scopes", scopes));
}
public String issueToken(
String subject,
Map<String, Object> claims
) {
var currentTime = LocalDateTime.now();
var expiredWithinMinutes = Integer.parseInt(Objects.requireNonNull(env.getProperty("jwt.token.expires.minutes")));
return Jwts
.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(Date.from(currentTime.atZone(ZoneId.systemDefault()).toInstant()))
.setExpiration(Date.from(currentTime.plusMinutes(expiredWithinMinutes).atZone(ZoneId.systemDefault()).toInstant()))
.signWith(getSigningKey(), SignatureAlgorithm.HS512)
.compact();
}
private Key getSigningKey() {
var secretInBytes = Objects.requireNonNull(env.getProperty("jwt.secret.key")).getBytes();
byte[] bytesEncoded = Base64.getEncoder().encode(secretInBytes);
return Keys.hmacShaKeyFor(bytesEncoded);
}
}
I would also recommend installing Spring CLI, then creating a keystore like :
keytool -genkeypair -alias myKeyAlias -keyalg RSA \ -dname "CN=Local,OU=Local,O=My laptop,L=Hyderabad,S=Telangana,C=India" \ -keypass keyPassword -keystore server.jks -storepass storePassword
then encrypt our secret and use {cipher} instead.... but why bother?
Here would be a tricky one.
Open your guard directory in a terminal window, and enter the following:
ng generate guard Access
In the app.module.ts file, in the bottom, paste the code:
export class AppModule {
constructor(private injector: Injector) {
AppInjector = this.injector;
}
}
Right after all imports and before @NgModule paste the piece:
export let AppInjector: Injector;
you would also need to import Injector
import { Injector, NgModule } from "@angular/core";
I leave my complete authentication.service.ts,
import { Inject, Injectable, Injector } from "@angular/core";
import { AuthenticationRequest } from "../../models/authentication-request";
import { AuthenticationResponse } from "../../models/authentication-response";
import { Observable } from "rxjs";
import { HttpClient } from "@angular/common/http";
import { JwtHelperService } from "@auth0/angular-jwt";
import { Router } from "@angular/router";
@Injectable({
providedIn: "root"
})
export class AuthenticationService {
constructor(
private http: HttpClient,
private router: Router,
) {
}
authenticate(credentialsForLogin: AuthenticationRequest): Observable<AuthenticationResponse> {
return this.http.post<AuthenticationResponse>(
"http://localhost:8080/api/v1/auth/login",
credentialsForLogin
);
}
setSession(authenticationResponse: AuthenticationResponse): void {
localStorage.setItem("userId", `${authenticationResponse.customerDto.id}`);
localStorage.setItem("token", authenticationResponse.token);
}
isSessionValid(): boolean {
const userId: number = Number(localStorage.getItem("userId"));
const token: string = localStorage.getItem("token");
if (this.isLocalStorageInfringed(userId, token) || this.isTokenExpired(token)) {
this.logout();
return false;
}
return true;
}
private logout(): void {
localStorage.clear();
this.router.navigate(["login"])
}
private isTokenExpired(token: string): boolean {
return new JwtHelperService().isTokenExpired(token);
}
private isLocalStorageInfringed(userId: number, token: string): boolean {
return !userId || !token;
}
}
in login.component.ts insert in constructor
if (authenticationService.isSessionValid()) {
router.navigate(["customer"]);
}
the login function in the same file has setSeesion method you need to place
next: authenticationResponse => {
this.authenticationService.setSession(authenticationResponse);
this.router.navigate(["customer"]);
},
Now the Guard itself.... I just don't know how to create an instance in the method... It complains on httpClient, then router.... so I've found a solution relying on injection
import { CanActivateFn } from "@angular/router";
import { AuthenticationService } from "../authentication/authentication.service";
import { AppInjector } from "../../app.module";
export const accessGuard: CanActivateFn = (route, state) => {
return AppInjector.get(AuthenticationService).isSessionValid()
};
Hello! I came across your code and I was wondering if you have considered adding a license file to your repository. A license file is a way of letting others know how they can use or modify your code legally. Without a license file, your code is technically not open source and no one can use it without your permission. This might limit the potential impact and reach of your project. GitHub recommends that you include a license in your repository to let others know how can your code be used and built upon (Source: Licensing a repository). If you are interested in making your project open source, you can check out Choosing a license to learn more about the different types of licenses and their benefits.
To add a license file to your repository, you can follow the steps in this guide: Adding a license to a repository. You can also read this article for more information on open source licensing: Open Source Licensing Guide.
There is a couple of things to be aware of:
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
The complete plugin part:
<plugin>
<groupId>com.google.cloud.tools</groupId>
<artifactId>jib-maven-plugin</artifactId>
<version>3.3.1</version>
<configuration>
<from>
<image>eclipse-temurin:20-jre</image>
<platforms>
<platform>
<architecture>arm64</architecture>
<os>linux</os>
</platform>
<platform>
<architecture>amd64</architecture>
<os>linux</os>
</platform>
</platforms>
</from>
<to>
<image>docker.io/${project.organization.name}/${project.artifactId}:${project.version}</image>
<tags>
<tag>latest</tag>
</tags>
</to>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
Now, you're good to go with mvn clean package
According to the Chakra UI's docs there is no disabled attribute in the Button. Its substitute is isDisabled
I would also suggest destructuring formik and extracting the variable of dirty
({isValid, isSubmitting, dirty}) =>
<Button type="submit" isDisabled={ !dirty || !isValid || isSubmitting }>Submit</Button>
The button would be disabled once the page is loaded.
Both methods shown in the first part of the course are verbose and consist of the unnecessary logic.
Whenever we work with an API, we have a documentation in which one can find endpoints and how payload should look like
In case of a PUT request there is no need in updating one of the fields since PUT would modifies the whole resource anyway.
What you do relates more to PATCH request. Whenever you just want to modify a part of the resource. Let's say, the name
{
"name": "john doe"
}
The issue is... it won't work as shown in the lesson:
sed -i -E 's_(amigoscode/amigoscode-api:)([^"]*)_\1'123456789'_' Dockerrun.aws.json
if you want it to work with the -E flag (extended mode), place a pair of single quote between two flags.
sed -i '' -E 's_(amigoscode/amigoscode-api:)([^"]*)_\1'123456789'_' Dockerrun.aws.json
or you may not use the extended mode you therefore need to escape each braces:
sed -i s_\(hazartilirot/hazartilirot-api:\)\([^\"]*\)_\1'123456789'_' Dockerrun.aws.json
Well, I don't know what he does behind the scene you won't get a list of customers once a user has been registered.
I've taken a screenshot to show the code in the lesson
The issue is simple as this one. When a user signs up as we don't save a token.
If you look at his last release there is a function onSuccess
onSubmit={(customer, {setSubmitting}) => {
setSubmitting(true);
saveCustomer(customer)
.then(res => {
console.log(res);
successNotification(
"Customer saved",
`${customer.name} was successfully saved`
)
onSuccess(res.headers["authorization"]);
}).catch(err => {
console.log(err);
errorNotification(
err.code,
err.response.data.message
)
}).finally(() => {
setSubmitting(false);
})
}}
While typing the message I just unable to figure out the meaning of the snippet:
onSuccess(res.headers["authorization"]);
onSuccess is fetchCustomers() function which doesn't accept arguments. What's the meaning of the code I just don't know.
I did basically as following:
onSubmit={
(customer, { setSubmitting }) => {
setSubmitting(true);
registerCustomer(customer)
.then(res => {
notifyOnSuccess("Customer saved", `${customer.name} was saved`);
return login({
username: customer.email,
password: customer.password
})
}).then(res => location.reload())
.catch(err => notifyOnFailure(err.code, err.response.data.error))
.finally(() => setSubmitting(false))
}
}
Don't forget to import the useAuth() to the JSX component
const { login } = useAuth();
In nutshell, we register a new user, then we run our login function, we pass username and password and then reload the page. I have no idea if it is implemented in a correct way - but it works!
Actually, the solution shown in the video is incorrect since we have protected the route /api/v1/customers
We need to add a token each time we request. There should be something like the following:
findAll(): Observable<CustomerDto[]> {
return this.http.get<CustomerDto[]>(
"http://localhost:8080/api/v1/customers", {
headers: new HttpHeaders({
"Authorization": `Bearer ${localStorage.getItem("token")}`
})
}
);
}
For no reason a piece of code has been cut out of the video. I figured out on my own how it should be written in order to get the result
<input type="text" [(ngModel)]="inputValue">
<button (click)="clickMe()">click</button>
<p>Your name is: {{inputValue}}</p>
<p *ngFor="let msg of msgList">{{ msg }}</p>
import { Component } from '@angular/core';
@Component({
selector: 'app-my-first-component',
templateUrl: './my-first.component.html',
styleUrls: ['./my-first.component.scss']
})
export class MyFirstComponent {
inputValue: string = '';
msgList: string[] = [];
clickMe = (): void => {
this.msgList.push(this.inputValue);
this.inputValue = '';
}
}
Basically, if you open the official docker postgres page there would be the subject's description.
You just need to add this line to your compose.yml file in order to create the default database customer
POSTGRES_DB: customer
version: "3.9"
services:
db:
container_name: postgres
image: postgres:latest
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: root
POSTGRES_DB: customer
PGDATA: /data/postgres
volumes:
- db:/data/postgres
ports:
- 5432:5432
networks:
- db
restart: unless-stopped
networks:
db:
driver: bridge
volumes:
db:
That's really strange! We've got a block @beforeeach setup.... why don't we place the piece of code into it?
var customer = new Customer(
FAKER.name().fullName(),
FAKER.internet().safeEmailAddress(),
new Random().nextInt(18, 99)
);
customerJDBCDataAccessServiceUnitTest.insertCustomer(customer);
Why does he use the email address to find the customer we've just inserted? He basically relies on a method he hasn't tested yet. It's a shame!
If the previous test is passed we mainly need to rely on it instead.
@Test
void selectCustomerById() {
var customer1 = customerJDBCDataAccessServiceUnitTest.selectAllCustomers()
.stream()
.findAny()
.orElseThrow();
var customer2 = customerJDBCDataAccessServiceUnitTest.selectCustomerById(customer1.getId())
.orElseThrow();
assertThat(customer1.getId()).isEqualTo(customer2.getId());
}
We don't care what email our inserted user has.... All we care about is if there is a user at all. We get the user and then we test our method looking the user by its ID. Here is the code:
@BeforeEach
void setUp() {
customerJDBCDataAccessServiceUnitTest = new CustomerJDBCDataAccessService(getJdbcTemplate());
var customer = new Customer(
FAKER.name().fullName(),
FAKER.internet().safeEmailAddress(),
new Random().nextInt(18, 99)
);
customerJDBCDataAccessServiceUnitTest.insertCustomer(customer);
}
@Test
void selectAllCustomers() {
var customers = customerJDBCDataAccessServiceUnitTest.selectAllCustomers();
assertThat(customers).isNotEmpty();
}
@Test
void selectCustomerById() {
var customer1 = customerJDBCDataAccessServiceUnitTest.selectAllCustomers()
.stream()
.findAny()
.orElseThrow();
var customer2 = customerJDBCDataAccessServiceUnitTest.selectCustomerById(customer1.getId())
.orElseThrow();
assertThat(customer1.getId()).isEqualTo(customer2.getId());
}
Lesson Redirect to Dashboard if logged in, timeline 02:00
Oh my gooooood. The more I watch the less I like.... Mama Samba is soooooooo impatient and unreasonable. I have just implemented the feature of fetching a user by id. Instead of using the null in the initial state in AuthContext.jsx - I used a blank object as a kind of a guest with ROLE_GUEST. I decided to go this way so that Sidebar.jsx is loaded properly.
AuthContext.jsx
import { createContext, useContext, useEffect, useState } from "react";
import { executeLogin, getCustomerById } from "../../services/client.js";
import jwtDecode from "jwt-decode";
import { redirect } from "react-router-dom";
const AuthContext = createContext({});
const AuthProvider = ({ children }) => {
let defaultCustomer = {
name: "Viewer",
email: "",
age: undefined,
gender: "",
roles: ["ROLE_GUEST"]
};
const [customer, setCustomer] = useState(defaultCustomer);
useEffect(() => {
getCustomer().then(data => setCustomer({ ...data }))
}, []);
const getCustomer = async () => {
const token = localStorage.getItem("access_token");
const id = localStorage.getItem("customer_id");
if (!token || !id) {
return logout();
}
const { data } = await getCustomerById(id)
const decodedJwt = jwtDecode(token);
if (!data.email === decodedJwt.sub) {
logout();
}
return data;
}
const login = async submitEmailPassword => new Promise(
(resolve, reject) => executeLogin(submitEmailPassword)
.then(res => {
const jwt = res.headers["authorization"];
localStorage.setItem("access_token", jwt);
localStorage.setItem("customer_id", res.data.customerDto.id);
getCustomer().then(fetched => {
setCustomer({ ...fetched })
resolve(res);
});
}).catch(err => {
reject(err);
})
);
const logout = () => {
localStorage.removeItem("access_token");
localStorage.removeItem("customer_id");
setCustomer(defaultCustomer);
redirect("/")
};
const isCustomerAuthenticated = () => {
const token = localStorage.getItem("access_token");
const id = localStorage.getItem("customer_id");
if (!token || !id) {
logout();
return false
}
const { exp } = jwtDecode(token);
if (Date.now() > exp * 1000) {
logout();
return false;
}
return true;
};
return (
<AuthContext.Provider value={{
customer,
login,
logout,
isCustomerAuthenticated
}}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);
export default AuthProvider;
export const getCustomerById = async id => {
try {
return await axios.get(
`${import.meta.env.VITE_API_BASE_URL}/api/v1/customers/${id}`,
getTokenFromLocalStorage()
);
} catch (e) {
throw e;
}
}
Each request I take the user's id from the localStorage, fetch the user from the database by the id and place the user into the state. Before that I retrieve the user's email from JWT and compare it with the customer's email - if they are the same - ok. If any of localStorage values have been removed from the client's side (jwt or user id) I redirect the user to the login page and delete any data in their localStorage.
What he does in the Login component is he checks if the user is null (state). The difference is he doesn't fetch the user from the database (there is no async request) and it seems to have worked perfectly well. In my case, by the time it checks the user it is yet GIMMICK (a guest! or it would've been null if I have done the same as he does in the video). Anyway, what we actually need to check is not the user's state, but if the user is authenticated:
Login.jsx
const { isCustomerAuthenticated } = useAuth();
const navigate = useNavigate();
useEffect(() => {
if (isCustomerAuthenticated()) {
navigate("dashboard")
}
}, [])
or, an alternative way....:
const navigate = useNavigate();
useEffect(() => {
const token = localStorage.getItem("access_token");
const id = localStorage.getItem("customer_id");
if (token && id) {
navigate("dashboard")
}
}, [])
if you're going to implement the same, there are extra two pieces of code:
onSubmit={(values, { setSubmitting }) => {
setSubmitting(true);
login(values)
.then(res => {
navigate("dashboard");
}).catch(err => notifyOnFailure(err.code, err.response?.data.message)
).finally(() => setSubmitting(false));
}}
onSubmit={
(customer, { setSubmitting }) => {
setSubmitting(true);
registerCustomer(customer)
.then(res => {
notifyOnSuccess("Customer saved", `${customer.name} was saved`);
return login({
username: customer.email,
password: customer.password
})
}).then(res => location.reload())
.catch(err => notifyOnFailure(err.code, err.response.data.error))
.finally(() => setSubmitting(false))
}
}
One is used when a user logs in to their account, another one is when a user signs up.
Okay, the issue I had personally (you most likely have the same) I didn't use his settings in docker-compose.yml file for database.
I actually had a container with many databases running so I decided to stick to my existent database.
docker run -d --name YOUR_DOCKER_CONTAINER_NAME-api --rm -p 8080:8080 --network DATABASE_NETWORK YOUR_DOCKER_ACCOUNT/YOUR_CONTAINER_NAME-api --spring.datasource.url=jdbc:postgresql://THING_WE_ARE_LOOKING_FOR:5432/customer
-d - detached mode
--name YOUR_DOCKER_CONTAINER_NAME-api - in your case it might be different.
--network DATABASE_NETWORK -
if you have your database container running, just use the command:
docker network ls
it will list all of your networks. You're interested in the Name of your database ID's container. In my case it's hibernate-starter_default Basically, we need to connect the container to this bridge.
YOUR_DOCKER_ACCOUNT/YOUR_CONTAINER_NAME-api - see the info in the docker repository it is a new container you have pushed.
--spring.datasource.url=jdbc:postgresql://THING_WE_ARE_LOOKING_FOR:5432/customer - it's really tricky one.
docker inspect ID_DATABASE_CONTAINER -f "{{json .NetworkSettings.Networks }}"
It will give you something like this:
{"hibernate-starter_default":{"IPAMConfig":null,"Links":null,"Aliases":["hibernate-starter-db-1","db","8543027d7b75"],"NetworkID":"4ad36a6716d855dfc93e1192acfae8024fa043b5118602559b5d6f130ed9994d","EndpointID":"7d4f4d8c66f7213a513774952a3ea55c790d0715a705eacd5fc01e0ede6e45b5","Gateway":"172.18.0.1","IPAddress":"172.18.0.2","IPPrefixLen":16,"IPv6Gateway":"","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"MacAddress":"02:42:ac:12:00:02","DriverOpts":null}}
You may use any Aliases name, like hibernate-starter-db-1 or db or 8543027d7b75 so in my case, I use
--spring.datasource.url=jdbc:postgresql://db:5432/customer
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.