I've written a small plugin to respond 403 to requests that don't have a userId field in JWT token
class LoggedInUserAuthConfiguration(
)
val LoggedInUserAuthPlugin =
createRouteScopedPlugin(name = "LoggedInUserAuthPlugin", ::LoggedInUserAuthConfiguration) {
with(pluginConfig) {
on(AuthenticationChecked) { call ->
call.principal<JWTPrincipal>()?.let {
call.userId ?: call.respond(HttpStatusCode.Forbidden)
} ?: call.respond(HttpStatusCode.Unauthorized)
}
}
}
I've written 2 functions to use this plugin but none of them work only on route level
fun Route.withLoggedInUser(build: suspend PipelineContext<Unit, ApplicationCall>.() -> Unit) {
install(LoggedInUserAuthPlugin) {}
handle { build() }
}
fun Route.authenticateLoggedInUser(build: suspend Route.() -> Unit) {
install(LoggedInUserAuthPlugin) {}
handle { this#authenticateLoggedInUser.build() }
}
I'm using them like this
fun Route.addRoute() {
authenticateLoggedInUser {
route("/endpoint") {
post {
...
}
get {
...
}
}
}
}
OR
fun Route.addRoute() {
route("/endpoint") {
method(HttpMethod.Post) {
withLoggedInUser {
...
}
}
method(HttpMethod.Get) {
withLoggedInUser {
...
}
}
}
}
This is working OK for the required route, but other routes are being intercepted by this plugin too
I've checked that installIntoRoute() is being called in ktor source
if (this is Route && plugin is BaseRouteScopedPlugin) {
return installIntoRoute(plugin, configure)
}
Firstly, the authenticateLoggedInUser definition is incorrect because you build a route (its children) in a handler. The build function must be called only once while routing is being built. The handler is called each time a matching request is made.
fun Route.authenticateLoggedInUser(build: Route.() -> Unit) {
install(LoggedInUserAuthPlugin) {}
build()
}
Secondly, in your first use case of the authenticateLoggedInUser inside the Route.addRoute method, the plugin is installed into a route where this method is called. Here is an example where your plugin affects the /another route:
authenticate("auth-jwt") {
addRoute()
get("/another") {
call.respondText { "OK" }
}
}
// ...
fun Route.addRoute() {
authenticateLoggedInUser {
route("/endpoint") {
post {
call.respondText { "post" }
}
get {
call.respondText { "get" }
}
}
}
}
To solve this problem, install your plugin into individual routes or the /endpoint route:
fun Route.addRoute() {
route("/endpoint") {
authenticateLoggedInUser {
post {
call.respondText { "post" }
}
get {
call.respondText { "get" }
}
}
}
}
Related
Let's say that I have controller with two routes:
#Controller('events')
export class EventController {
#Get('my')
async getMyEvents() {
return "A"
}
#Get(':eventId')
async getEvent(#Param('eventId', ParseUUIDPipe) eventId: string) {
return "B"
}
}
and I need to exclude all routes except one which have param:
export class EventModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthMiddleware)
.exclude({path: 'api/events/:eventId', method: RequestMethod.GET})
.forRoutes(EventController)
}
}
but it doesn't work, it also exclude route api/events/my, so how to avoid that ?
add the routes in exclude, you want to exclude and the rest in forRoutes
export class EventModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthMiddleware)
.exclude("*")
.forRoutes({path: 'api/events/:eventId', method: RequestMethod.GET})
}
}
I have the following code:
Completable.fromCallable { messagesBO.deleteAllGroupsForMessage(messageId) }
.andThen { Completable.fromAction { messagesBO.storeMessageGroups(messageToGroups) }.subscribe() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
},
{ error -> }
))
I have a second Completable:
Completable.fromAction
and I need to subscribe to this in order for the function inside fromAction to run. But this seems to not be a clean way of doing this.
I don't necessarily need to use a Completable as I'm not returning anything in the onComplete. Is there some other way to call a function and have it automatically subscribed to? I could in fact have multiple andThen(s) and would like to chain them together.
UPDATE:
Here is one solution, although I'm not convinced it's the best:
Observable.fromCallable { messagesBO.deleteAllGroupsForMessage(messageId) }
.flatMapCompletable { Completable.fromAction { messagesBO.storeMessageGroups(messageToGroups) } }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
},
{ error -> }
))
Turns out that it's simpler than I thought. Just using map will automatically run the code without any need to create an observable:
Observable.fromCallable { messagesBO.deleteAllGroupsForMessage(messageId) }
.map { messagesBO.storeMessageGroups(messageToGroups) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
},
{ error -> }
)
I want to ask that how can we return a promise from the subscribe function.
Here is the code:
A.ts
makeHttpRequest() {
return this.http.get('https://example.com/login');
}
B.ts
class B {
constructor(private a: A) {
this.a.makeHttpRequest().subscribe(data => {
//How to return a promise from here
});
}
}
I have provided a very abstract level of code if someone is facing any issue to understand it please let me know.
I have developed the solution of question asked above:
class B {
constructor(private a: A) {
return new Promise((resolve,reject)=>{
this.a.makeHttpRequest().subscribe(data => {
resolve(true);
}, (err)=> {
resolve(false);
});
});
}
}
Here is my code on my mutation
class CreateUserMutation extends Relay.Mutation {
static fragments = {
user: () => Relay.QL`
fragment on User{
id
email
}
`
}
getMutation () {
return Relay.QL`mutation{ createUser }`
}
getVariables() {
return {
email: 'bondan#something.com'
}
}
getFatQuery() {
return Relay.QL`
fragment on CreateUserPayload {
user{
email
}
}
`
}
getConfigs () {
// console.log('getConfigs props', this.props);
return [{
type: 'FIELDS_CHANGE',
fieldIDs: {
user: this.props.user.id
}
}]
}
}
and this is my implementation on UI
componentWillMount () {
let mutation = new CreateUserMutation();
Relay.Store.commitUpdate(mutation);
}
its a simple experiment apps just to check if my mutation is working when the app load
however the i keep getting this error
user is not defined
Please help me what is wrong with the mutation code?
I've added an authorize pipeline step to my router. Everything works fine, but when I use the Redirect class to point the user to the login page, it takes a URL as its argument. I'd prefer to pass in the route name as I would if I were using Router.navigateToRoute(). Is this possible?
#inject(AuthService)
class AuthorizeStep {
constructor (authService) {
this.authService = authService;
}
run (navigationInstruction, next) {
if (navigationInstruction.getAllInstructions().some(i => i.config.auth)) {
if (!this.authService.isLoggedIn) {
return next.cancel(new Redirect('url-to-login-page')); // Would prefer to use the name of route; 'login', instead.
}
}
return next();
}
}
After some Googling I found the Router.generate() method which takes a router name (and optional params) and returns the URL. I've now updated my authorize step to the following:
#inject(Router, AuthService)
class AuthorizeStep {
constructor (router, authService) {
this.router = router;
this.authService = authService;
}
run (navigationInstruction, next) {
if (navigationInstruction.getAllInstructions().some(i => i.config.auth)) {
if (!this.authService.isLoggedIn) {
return next.cancel(new Redirect(this.router.generate('login')));
}
}
return next();
}
}
Edit: After some more googling I found the RedirectToRoute class;
import { RedirectToRoute } from 'aurelia-router';
...
return next.cancel(new RedirectToRoute('login'));