Swagger with NestJS won't attach Bearer token for some endpoints - swagger

I have a NestJS controller:
#Controller('users')
#ApiTags('users')
export class UserController {
constructor(
private userService: UserService,
private userRoleService: UserRoleService,
private readonly translationService: TranslationService,
) {}
#Get('organization-admin')
#AuthorizeFor(RoleType.ORGANIZATION_ADMIN)
#HttpCode(HttpStatus.OK)
#ApiOkResponse({
description: 'Access for organization admin granted'
})
async organizationAdmin(#AuthUser() user: UserEntity): Promise<string> {
return `Hello organization admin ${user.firstName}`;
}
#Post(':id/assign-role')
#AuthorizeFor(RoleType.ORGANIZATION_ADMIN)
#HttpCode(HttpStatus.OK)
#ApiOkResponse({
status: HttpStatus.OK,
description: 'Role assigned to user',
type: UserRoleDto,
})
async assignRoleToUser(
#Body() userRole: CreateUserRoleDto,
#UUIDParam('id') userId: string
): Promise<CreateUserRoleDto> {
const user = await this.userService.findOne({ id: userId });
return this.userRoleService.createUserRole(user, userRole);
}
}
Using swagger - Bearer token is attached for request #Get('organization-admin') but won't for #Post(':id/assign-role').
Could you please help me to figure out why it can happen?
Thanks in advance.

Try add #ApiBearerAuth() decorator to your endpoints/controller

Related

Can't tweet on twitter by using supabase user access token

I am using supabase/auth-helpers with sveltekit and using Twitter OAuth, and the sign up and login are working just fine.
I am using the twitter-api-v2 package, and it states that I can use access token as bearer token like so:
const client = new TwitterApi(MY_BEARER_TOKEN);
Here is my code:
import { variables } from '$lib/variables';
import { supabaseServerClient, withApiAuth } from '#supabase/auth-helpers-sveltekit';
import type { RequestHandler } from '#sveltejs/kit';
import { TwitterApi } from 'twitter-api-v2';
export const GET: RequestHandler<any> = async ({ locals }) =>
withApiAuth(
{
redirectTo: '/',
user: locals.user
},
async () => {
const loggedClient = new TwitterApi(locals.accessToken!, {
clientId: variables.twitter_oauth_client_id,
clientSecret: variables.twitter_oauth_secret
} as any);
const profile = await loggedClient.v2.me();
return {
body: {
data: profile as any
}
};
}
);
However, I am getting error 403 and sometimes 401. The error output reads:
Request failed with code 401
Here is a link to twitter api response codes: https://developer.twitter.com/en/support/twitter-api/error-troubleshooting

Swagger endless loading when i click in Execute

I am using Swagger with keycloak. I am getting the below error when I click on Execute button.
when I click on Execute on swagger I see the loading image, and there is no any request on the network tap because I have an error on the console.
I will add my config code and YML file to allow you to see what I do.
anyone can help me, please?
Here is the output in the console:
Here is the error code in the console tap (Its generated code):
here is the swagger UI page:
here is the swagger config:
#Slf4j
#Configuration
#TbCoreComponent
public class SwaggerConfiguration {
#Value("${swagger.api_path_regex}")
private String apiPathRegex;
#Value("${swagger.security_path_regex}")
private String securityPathRegex;
#Value("${swagger.non_security_path_regex}")
private String nonSecurityPathRegex;
#Value("${swagger.title}")
private String title;
#Value("${swagger.description}")
private String description;
#Value("${swagger.contact.name}")
private String contactName;
#Value("${swagger.contact.url}")
private String contactUrl;
#Value("${swagger.contact.email}")
private String contactEmail;
#Value("${swagger.version}")
private String version;
#Value("${app.version:unknown}")
private String appVersion;
// Used to get token from Keyclaok
#Value("${swagger.auth.token_url}")
private String keycloakAuthTokenUrl;
#Value("${security.keycloak.realm}")
private String keycloakRealm;
#Value("${security.keycloak.clientId}")
private String keyclaokAuthCliendId;
#Bean
public Docket yousefApi() {
TypeResolver typeResolver = new TypeResolver();
return new Docket(DocumentationType.SWAGGER_2)
.groupName("Hammad")
.apiInfo(apiInfo())
.additionalModels(
typeResolver.resolve(ThingsboardErrorResponse.class),
typeResolver.resolve(ThingsboardCredentialsExpiredResponse.class)
)
.select()
.paths(apiPaths())
.paths(any())
.build()
.globalResponses(HttpMethod.GET, defaultErrorResponses(false))
.globalResponses(HttpMethod.POST, defaultErrorResponses(true))
.globalResponses(HttpMethod.DELETE, defaultErrorResponses(false))
.securitySchemes(newArrayList(apiKey()))
.securityContexts(newArrayList(securityContext()))
.enableUrlTemplating(true);
}
#Bean
#Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER)
ApiListingBuilderPlugin loginEndpointListingBuilder() {
return new ApiListingBuilderPlugin() {
#Override
public void apply(ApiListingContext apiListingContext) {
if (apiListingContext.getResourceGroup().getGroupName().equals("Hammad")) {
ApiListing apiListing = apiListingContext.apiListingBuilder().build();
if (apiListing.getResourcePath().equals(keycloakAuthTokenUrl)) {
apiListingContext.apiListingBuilder().tags(Set.of(new Tag("login-endpoint", "Login Endpoint")));
apiListingContext.apiListingBuilder().description("Login Endpoint");
}
}
}
#Override
public boolean supports(#NotNull DocumentationType delimiter) {
return DocumentationType.SWAGGER_2.equals(delimiter) || DocumentationType.OAS_30.equals(delimiter);
}
};
}
#Bean
UiConfiguration uiConfig() {
return UiConfigurationBuilder.builder()
.deepLinking(true)
.displayOperationId(false)
.defaultModelsExpandDepth(1)
.defaultModelExpandDepth(1)
.defaultModelRendering(ModelRendering.EXAMPLE)
.displayRequestDuration(false)
.docExpansion(DocExpansion.NONE)
.filter(false)
.maxDisplayedTags(null)
.operationsSorter(OperationsSorter.ALPHA)
.showExtensions(false)
.showCommonExtensions(false)
.supportedSubmitMethods(UiConfiguration.Constants.DEFAULT_SUBMIT_METHODS)
.validatorUrl(null)
.persistAuthorization(true)
.syntaxHighlightActivate(true)
.syntaxHighlightTheme("agate")
.build();
}
private ApiKey apiKey() {
return new ApiKey("Bearer", "X-Authorization", "header");
}
private OAuth securityScheme() {
List<GrantType> grantTypes = newArrayList(new ResourceOwnerPasswordCredentialsGrant(keycloakAuthTokenUrl));
return new OAuth("KeycloakAuth", new ArrayList<>(), grantTypes);
}
private SecurityContext securityContext() {
return SecurityContext.builder()
.securityReferences(defaultAuth())
.operationSelector(securityPathOperationSelector())
.build();
}
private Predicate<String> apiPaths() {
return regex(apiPathRegex);
}
private Predicate<OperationContext> securityPathOperationSelector() {
return new SecurityPathOperationSelector(securityPathRegex, nonSecurityPathRegex);
}
private AuthorizationScope[] scopes() {
AuthorizationScope[] authorizationScopes = new AuthorizationScope[3];
authorizationScopes[0] = new AuthorizationScope(Authority.SYS_ADMIN.name(), "System administrator");
authorizationScopes[1] = new AuthorizationScope(Authority.TENANT_ADMIN.name(), "Tenant administrator");
authorizationScopes[2] = new AuthorizationScope(Authority.CUSTOMER_USER.name(), "Customer");
return authorizationScopes;
}
List<SecurityReference> defaultAuth() {
return newArrayList(new SecurityReference("KeycloakAuth", scopes()), new SecurityReference("Bearer", scopes()));
}
private ApiInfo apiInfo() {
String apiVersion = version;
if (StringUtils.isEmpty(apiVersion)) {
apiVersion = appVersion;
}
return new ApiInfoBuilder()
.title(title)
.description(description)
.contact(new Contact(contactName, contactUrl, contactEmail))
.version(apiVersion)
.build();
}
/** Helper methods **/
private List<Response> defaultErrorResponses(boolean isPost) {
return List.of(
errorResponse("400", "Bad Request",
ThingsboardErrorResponse.of(isPost ? "Invalid request body" : "Invalid UUID string: 123", ThingsboardErrorCode.BAD_REQUEST_PARAMS, HttpStatus.BAD_REQUEST)),
errorResponse("401", "Unauthorized",
ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
errorResponse("403", "Forbidden",
ThingsboardErrorResponse.of("You don't have permission to perform this operation!",
ThingsboardErrorCode.PERMISSION_DENIED, HttpStatus.FORBIDDEN)),
errorResponse("404", "Not Found",
ThingsboardErrorResponse.of("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND, HttpStatus.NOT_FOUND)),
errorResponse("429", "Too Many Requests",
ThingsboardErrorResponse.of("Too many requests for current tenant!",
ThingsboardErrorCode.TOO_MANY_REQUESTS, HttpStatus.TOO_MANY_REQUESTS))
);
}
private Response errorResponse(String code, String description, ThingsboardErrorResponse example) {
return errorResponse(code, description, List.of(errorExample("error-code-" + code, description, example)));
}
private Response errorResponse(String code, String description, List<Example> examples) {
return errorResponse(code, description, examples, ThingsboardErrorResponse.class);
}
private Response errorResponse(String code, String description, List<Example> examples,
Class<? extends ThingsboardErrorResponse> errorResponseClass) {
return new ResponseBuilder()
.code(code)
.description(description)
.examples(examples)
.representation(MediaType.APPLICATION_JSON)
.apply(classRepresentation(errorResponseClass, true))
.build();
}
private Example errorExample(String id, String summary, ThingsboardErrorResponse example) {
return new ExampleBuilder()
.mediaType(MediaType.APPLICATION_JSON_VALUE)
.summary(summary)
.id(id)
.value(example).build();
}
private Consumer<RepresentationBuilder> classRepresentation(Class<?> clazz, boolean isResponse) {
return r -> r.model(
m ->
m.referenceModel(ref ->
ref.key(k ->
k.qualifiedModelName(q ->
q.namespace(clazz.getPackageName())
.name(clazz.getSimpleName())).isResponse(isResponse)))
);
}
private static class SecurityPathOperationSelector implements Predicate<OperationContext> {
private final Predicate<String> securityPathSelector;
SecurityPathOperationSelector(String securityPathRegex, String nonSecurityPathRegex) {
this.securityPathSelector = (not(regex(nonSecurityPathRegex)));
}
#Override
public boolean test(OperationContext operationContext) {
return this.securityPathSelector.test(operationContext.requestMappingPattern());
}
}
}
here is the YML file:
swagger:
auth:
token_url: ${security.keycloak.serverUrl}/realms/${security.keycloak.realm}/protocol/openid-connect/token/
auth_url: ${security.keycloak.serverUrl}/realms/${security.keycloak.realm}/protocol/openid-connect/auth/
api_path_regex: "${SWAGGER_API_PATH_REGEX:/api/(customer|device|user|tenant).*}"
security_path_regex: "${SWAGGER_SECURITY_PATH_REGEX:/api/(customer|device|user|tenant).*}"
non_security_path_regex: "${SWAGGER_NON_SECURITY_PATH_REGEX:/api/(?:noauth|v1)/.*}"
title: "${SWAGGER_TITLE:yousefCo REST API}"
description: "${SWAGGER_DESCRIPTION: yousefCo open-source IoT platform REST API documentation.}"
contact:
name: "${SWAGGER_CONTACT_NAME:yousefCo Team}"
url: "${SWAGGER_CONTACT_URL:http://iot.test.net}"
email: "${SWAGGER_CONTACT_EMAIL:info#gmail.com}"
license:
title: "${SWAGGER_LICENSE_TITLE:Apache License Version 2.0}"
url: "${SWAGGER_LICENSE_URL:https://github.com/yousef/yousef/blob/master/LICENSE}"
version: "${SWAGGER_VERSION:}"
I think you have mixed the configuration between swagger version 2 and 3, your overall setting is configured to be used in Swagger 3.0, so
First, You have to change the DocumentationType to OAS_30.
Second, When using the ApiKey security scheme, note that the field name will be used as the header when calling an API, so you have to change it to:
private ApiKey apiKey() {
return new ApiKey("X-Authorization", "AnyNameYouWant", "header");
}
Note that you have to add the Bearer word before the token, also you have to change the name SecurityReference to X-Authorization.
Third, If you need to config the OAuth2 security scheme, instead of using the OAuth class, use the OAuth2Scheme builder class with password flow, like this:
return OAuth2Scheme.OAUTH2_PASSWORD_FLOW_BUILDER
.name("KeycloakAuth")
.scopes(newArrayList(scopes()))
.tokenUrl(keycloakAuthTokenUrl)
.refreshUrl(keycloakAuthTokenUrl)
.build();
Don't forget to add the same scopes in SecurityReference into the OAuth2Scheme scopes.

Angular Auth OIDC; Azue B2C : Getting user profile loading and promise uncaught error after getting Token from Azure

I have implemented auth authentication in Angular 9 app with Azure B2C with using angular-oauth2-oidc library and have successfully managed to get token however I am getting 'Uncaught (in promise)' and error 'loading user info TypeError' error the same time.
user profile error
Promise Uncaught Error
Auth Service
#Injectable()
export class AuthService implements OnInit{
_accessToken: string;
_idToken: string;
private isAuthenticatedSubject$ = new BehaviorSubject<boolean>(false);
public isAuthenticated$ = this.isAuthenticatedSubject$.asObservable();
private isDoneLoadingSubject$ = new ReplaySubject<boolean>();
public isDoneLoading$ = this.isDoneLoadingSubject$.asObservable();
public canActivateProtectedRoutes$: Observable<boolean> = combineLatest([
this.isAuthenticated$,
this.isDoneLoading$
]).pipe(map(values => values.every(b => b)));
constructor(
private oauthService: OAuthService,
private router: Router
){
//debugging - Capture Error Events
this.oauthService.events.subscribe(event =>{
if(event instanceof OAuthErrorEvent){
console.error(event);
}
else{
console.warn(event);
}
});
console.warn('Noticed changes to access_token (most likely from another tab), updating isAuthenticated');
this.isAuthenticatedSubject$.next(this.oauthService.hasValidAccessToken());
this.oauthService.events
.subscribe(_ => {
this.isAuthenticatedSubject$.next(this.oauthService.hasValidAccessToken());
});
this.oauthService.events
.pipe(filter(e => ['token_received'].includes(e.type)))
.subscribe(e => this.oauthService.loadUserProfile());
this.oauthService.events
.pipe(filter(e => ['session_terminated', 'session_error'].includes(e.type)))
.subscribe(e => this.navigateToLoginPage());
this.oauthService.setupAutomaticSilentRefresh();
}
public initializeAuthService(): Promise<void>{
this.oauthService.configure(authConfig);
if (location.hash) {
console.log('Encountered hash fragment, plotting as table...');
console.table(location.hash.substr(1).split('&').map(kvp => kvp.split('=')));
}
return this.oauthService.loadDiscoveryDocument(DiscoveryDocumentConfig.url)
.then(() => this.oauthService.tryLogin())
.then(() => {
if(this.oauthService.hasValidAccessToken()){
console.log("Discovery document resolved, Token does exist...", this.oauthService.getAccessToken());
return Promise.resolve();
}
})
.catch(error => {
console.error(error);
})
}
public login(targetUrl?: string){
this.oauthService.tryLogin({});
if(!this.oauthService.getAccessToken()){
this.oauthService.initImplicitFlow();
}
}
This sample works with IdentityServer but I am using Azure AD B2C.
I commented following code in AuthService and everything seems to be working now:
// this.oauthService.events
// .pipe(filter((e) => ["token_received"].includes(e.type)))
// .subscribe((e) => this.oauthService.loadUserProfile());

AWS CDK | Create a REST API spanning multiple CDK Stacks

We are using AWS CDK to create our Serverless REST API. However, there are a large number of endpoints and sometimes we have to destroy and redeploy our stack. To prevent the REST API URL from changing with each deployment, I am planning to create the API GATEWAY in one stack and add methods and resources in a separate stack. How can I refer the created rest API in a separate stack?
Tried to implement something from https://github.com/aws/aws-cdk/issues/3705, but all of the resources(API Gateway, resource and methods) are being pushed in a single stack instead of API Gateway in one stack and the resources in other stack.
Relevant codes snippets are provided below:
bts-app-cdk.ts
const first = new FirstStack(app, 'FirstStack', {
env: {
region: 'us-east-1',
account: '1234567890',
}
});
const second = new SecondStack(app, 'SecondStack', {
apiGateway: first.apiGateway,
env: {
region: 'us-east-1',
account: '1234567890',
}
});
second.addDependency(first)
first-stack.ts
export class FirstStack extends cdk.Stack {
public readonly apiGateway: apig.IResource;
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const apiGateway = new apig.RestApi(this, 'BooksAPI', {
restApiName:'Books API',
})
apiGateway.root.addMethod('GET');
this.apiGateway = apiGateway.root;
}
}
second-stack
export interface SecondStackProps extends cdk.StackProps {
readonly apiGateway: apig.IResource;
}
export class SecondStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props: SecondStackProps) {
super(scope, id, props);
props.apiGateway.addMethod('ANY')
}
}
Seems that there is no way at the moment except using Cfn constructs, here's the github issue to track https://github.com/aws/aws-cdk/issues/1477
You have to set the deploy prop to false when instantiating your api-gw.
Then you can pass the restApiId and rootResourceId as enviroment variables to stacks.
Finally you can use a Deployment to deploy everything.
interface ResourceNestedStackProps extends NestedStackProps {
readonly restApiId: string;
readonly rootResourceId: string;
}
export class FirstStack extends cdk.Stack {
public readonly methods: Method[];
constructor(scope: cdk.Construct, props: ResourceNestedStackProps) {
super(scope, 'first-stack', props);
// get a hold of the rest api from attributes
const api = RestApi.fromRestApiAttributes(this, 'RestApi', {
restApiId: props.restApiId,
rootResourceId: props.rootResourceId,
})
// REPEAT THIS FOR METHODS
const method = api.root.addMethod('GET');
this.methods.push(method)
// ---
}
}
Then in the root stack file,
class RootStack extends Stack {
constructor(scope: Construct) {
const restApi = new RestApi(this, 'RestApi', {
deploy: false,
});
restApi.root.addMethod('ANY');
const firstStack = new FirstStack(this, {
restApiId: restApi.restApiId,
rootResourceId: restApi.restApiRootResourceId,
});
new DeployStack(this, {
restApiId: restApi.restApiId,
methods: [...firstStack.methods],
})
}
}
The deploy stack is where you deploy your API:
interface DeployStackProps extends NestedStackProps {
readonly restApiId: string;
readonly methods?: Method[];
}
class DeployStack extends NestedStack {
constructor(scope: Construct, props: DeployStackProps) {
super(scope, 'deploy-stack', props);
const deployment = new Deployment(this, 'Deployment', {
api: RestApi.fromRestApiId(this, 'RestApi', props.restApiId),
});
if (props.methods) {
for (const method of props.methods) {
deployment.node.addDependency(method);
}
}
new Stage(this, 'Stage', { deployment });
}
}
Refer this for more details: https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.NestedStack.html

Angular 2 cookie authentication

I am writing some integration between Angular 2(not a big expert in JS) and Rails 4.2. On Rails side I am using devise for authentication. The thing is when I am requesting to sign in user, Set-Cookie header is returned in response. But then, when I am trying to request any authentication-required resource Cookie header has value of "Set-Cookie=null"(like there is no value of Set-Cookie?). Here's how it's done:
session.service.ts
#Injectable()
export class SessionService{
private headers = new Headers({'Content-Type': 'application/json', 'Accept': 'application/json'})
private createSessionUrl = 'http://localhost:3002/users/sign_in';
constructor(
private http: Http
) { }
create(email: string, password: string): Promise<User>{
return this.http.post(this.createSessionUrl, JSON.stringify({user:{email: email, password: password}}),
{ headers: this.headers })
.toPromise()
.then(response => response.json() as User)
.catch(this.handleError)
}
private handleError(error: any): Promise<any> {
return Promise.reject(error.message || error);
}
}
statistics.service.ts
import { Injectable } from '#angular/core';
import { Headers, Http, RequestOptions } from '#angular/http';
import { Statistics } from '../models/statistics.models'
import 'rxjs/add/operator/toPromise';
#Injectable()
export class StatisticsService{
private headers = new Headers({'Content-Type': 'application/json', 'Accept': 'application/json'})
private statisticsUrl = 'http://localhost:3002/statistics';
constructor(
private http: Http
) {}
show(): Promise<Statistics>{
let options = new RequestOptions({ headers: this.headers, withCredentials: true });
return this.http.get(this.statisticsUrl, options)
.toPromise()
.then(response => response.json() as Statistics)
.catch(this.handleError)
}
private handleError(error: any): Promise<any> {
console.error('An error occurred', error);
return Promise.reject(error.message || error);
}
}
As far as I know, let options = new RequestOptions({ headers: this.headers, withCredentials: true });
should include proper Cookie in request.
If there is any extra info required, please write in comments.
your question is quite old, but I hope it helps anyway. I stumbled about that problem as well and wished to find your question answered. Now I take care of that.
I think you forgot the withCredentials option in the POST request of your session.service.ts. As you send a request with credentials (email and password) and you expect the response to set a cookie, you should tell the http-service about that:
return this.http.post(
this.createSessionUrl,
JSON.stringify({user:{email: email, password: password}}),
{ headers: this.headers, withCredentials: true }
)
Now the cookie should be stored by the browser and the next statistics request should contain the correct cookie.
Does this work?
Cheers,
Friedrich

Resources