Skip to content

Commit

Permalink
feat: add consentless login
Browse files Browse the repository at this point in the history
Now applications can login without asking for consent from the end user

This feature has been warned not to used because 1. asking for user consent is ethical
2. This might be not be bug-free as mentioned below

link: https://github.com/panva/node-oidc-provider/blob/v7.x/recipes/skip_consent.md
  • Loading branch information
Zolo-Ryan committed Sep 24, 2024
1 parent 261582e commit ad9612b
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 22 deletions.
4 changes: 3 additions & 1 deletion kickstart.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,9 @@
],
"clientSecret": "#{clientSecret}",
"enabledGrants": ["authorization_code", "refresh_token"],
"logoutURL": "http://localhost:3001/logout"
"logoutURL": "http://localhost:3001/logout",
"enablePKCE": false,
"skipConsentScreen": true
}
}
}
Expand Down
1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ model Application {
updatedAt DateTime @updatedAt
name String
tenantId String
logo_uri String? @default("https://www.flaticon.com/free-icon/application_2833637")
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
accessTokenSigningKey Key? @relation("ApplicationAccessTokenSigningKey", fields: [accessTokenSigningKeysId], references: [id])
Expand Down
18 changes: 18 additions & 0 deletions src/application/application.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,24 @@ class OauthConfiguration {
// @IsUrl({}, { message: 'Logout URL must be a valid URL' })
@IsNotEmpty({ message: 'Logout URL must not be empty' })
logoutURL: string;

@ApiProperty({
default: false,
type: Boolean,
example: 'true',
description: "Whether to ask user's consent for getting user claims or not",
})
@IsBoolean()
skipConsentScreen: boolean = false;

@ApiProperty({
default: false,
type: Boolean,
example: 'false',
description: 'enable proof key for code exchange for an application',
})
@IsBoolean()
enablePKCE: boolean = false;
}

export class JwtConfiguration {
Expand Down
15 changes: 10 additions & 5 deletions src/oidc/oidc.adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const types = [
'Interaction',
'ReplayDetection',
'PushedAuthorizationRequest',
'Grant',
'Grant', //13
'BackchannelAuthenticationRequest',
].reduce(
(map, name, i) => ({ ...map, [name]: i + 1 }),
Expand Down Expand Up @@ -71,7 +71,7 @@ export class PrismaAdapter implements Adapter {
uid: payload.uid,
expiresAt: expiresAt(expiresIn),
};

await prisma.oidcModel.upsert({
where: {
id_type: {
Expand Down Expand Up @@ -101,17 +101,22 @@ export class PrismaAdapter implements Adapter {
const clientData: ApplicationDataDto = JSON.parse(client.data);
const scope =
await PrismaAdapter.utilsService.returnScopesForAGivenApplicationId(id);

const formattedClientData: AdapterPayload = {
client_id: id,
client_secret: clientData.oauthConfiguration.clientSecret,
redirect_uris: clientData.oauthConfiguration.authorizedRedirectURLs,
grant_types: clientData.oauthConfiguration.enabledGrants,
client_name: client.name,
post_logout_redirect_uris: [clientData.oauthConfiguration.logoutURL], // check this, not having any effect
scope: scope.join(' '),
logo_uri: 'http://localhost:3000', // take this in schema of application, this is url to application image to display while login
logo_uri: client.logo_uri, // take this in schema of application, this is url to application image to display while login
extra: {
skipConsentScreen: clientData.oauthConfiguration.skipConsentScreen,
enablePKCE: clientData.oauthConfiguration.enablePKCE,
},
};

// console.log(formattedClientData);
return formattedClientData;
}
const doc = await prisma.oidcModel.findUnique({
Expand Down
94 changes: 82 additions & 12 deletions src/oidc/oidc.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { Injectable } from '@nestjs/common';
import Provider, { Configuration, JWKS, errors } from 'oidc-provider';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import Provider, {
AdapterPayload,
Client,
Configuration,
JWKS,
errors,
} from 'oidc-provider';
import { PrismaAdapter } from './oidc.adapter';
import { PrismaService } from 'src/prisma/prisma.service';
import { ApplicationDataDto } from 'src/application/application.dto';
Expand Down Expand Up @@ -78,29 +84,89 @@ export class OIDCService {
},
// this and next function are under progress
extraClientMetadata: {
properties: [corsProp],
properties: [corsProp, 'extra'], // don't remove extra, used for skipping consent screen
validator(ctx, key, value, metadata) {
console.log(ctx); // ctx undefined
// console.log('Inside validator===', key, value, metadata);
return;
if (key === corsProp) {
// set default (no CORS)
if (value === undefined) {
metadata[corsProp] = [];
return;
}
// validate an array of Origin strings
if (!Array.isArray(value) || !value.every(isOrigin)) {
throw new UnauthorizedException(
`${corsProp} must be an array of origins`,
);
}
}
},
},
clientBasedCORS(ctx, origin, client) {
console.log(ctx);
// ctx.oidc.route can be used to exclude endpoints from this behaviour, in that case just return
// true to always allow CORS on them, false to deny
// you may also allow some known internal origins if you want to
console.log(ctx.oidc.route);
console.log('THis is origin', origin);
return true;
return (client[corsProp] as any).includes(origin);
},
loadExistingGrant: async (ctx) => {
const clientMacroObject = ctx.oidc.client;
const grantId =
(ctx.oidc.result &&
ctx.oidc.result.consent &&
ctx.oidc.result.consent.grantId) ||
ctx.oidc.session.grantIdFor(ctx.oidc.client.clientId);

if (grantId) {
// keep grant expiry aligned with session expiry
// to prevent consent prompt being requested when grant expires
const grant = await ctx.oidc.provider.Grant.find(grantId);

// this aligns the Grant ttl with that of the current session
// if the same Grant is used for multiple sessions, or is set
// to never expire, you probably do not want this in your code
if (ctx.oidc.account && grant.exp < ctx.oidc.session.exp) {
grant.exp = ctx.oidc.session.exp;

await grant.save();
}

return grant;
} else if (OIDCService.skipConsent(clientMacroObject) === true) {
console.log("HI");
const grant = new ctx.oidc.provider.Grant({
clientId: ctx.oidc.client.clientId,
accountId: ctx.oidc.session.accountId,
});
const client = await OIDCService.prismaService.application.findUnique(
{
where: { id: clientMacroObject.clientId },
},
);
if (!client || !client.active) return undefined;

const scope =
await OIDCService.utilsService.returnScopesForAGivenApplicationId(
clientMacroObject.clientId,
);

grant.addOIDCScope(scope.join(' '));
// not needed
// grant.addOIDCClaims(['first_name']);
// grant.addResourceScope(
// 'urn:example:resource-indicator',
// 'api:read api:write',
// );
await grant.save();
return grant;
}
},
renderError: (ctx, out, error) => {
console.log('Error why not working: ', error);
},
pkce: {
methods: ['S256'],
required: () => false,
required: (ctx, client) => {
return (client.extra as any).enablePKCE === true ? true: false;
},
},
features: {
introspection: { enabled: true },
Expand Down Expand Up @@ -156,6 +222,11 @@ export class OIDCService {
return config;
}

private static skipConsent(client: Client) {
// if enabled skipConsentScreen in the application schema then skip consent
return (client.extra as any).skipConsentScreen === true ? true : false;
}

private static async returnTTL() {
const client_ttlMap: Map<string, ApplicationDataDto> = new Map();
const clients = await this.prismaService.application.findMany();
Expand Down Expand Up @@ -190,7 +261,6 @@ export class OIDCService {
const user = await OIDCService.prismaService.user.findUnique({
where: { email: id },
});
console.log('user: ', ctx.req.body, ctx.req.params);

if (!user) {
return undefined;
Expand Down
16 changes: 15 additions & 1 deletion swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -2885,14 +2885,28 @@
"type": "string",
"description": "Logout URL",
"example": "https://example.com/logout"
},
"skipConsentScreen": {
"type": "boolean",
"default": false,
"example": "true",
"description": "Whether to ask user's consent for getting user claims or not"
},
"enablePKCE": {
"type": "boolean",
"default": false,
"example": "false",
"description": "enable proof key for code exchange for an application"
}
},
"required": [
"authorizedOriginURLs",
"authorizedRedirectURLs",
"clientSecret",
"enabledGrants",
"logoutURL"
"logoutURL",
"skipConsentScreen",
"enablePKCE"
]
},
"CreateApplicationDto": {
Expand Down
13 changes: 10 additions & 3 deletions zolo.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ Read the above documentation and then implement the remaining functionalities.
- [x] 4a. Creating our own `interactions` page and removing `devInteractions`
- [ ] 4b. removing `renderError` with our own
- [x] 5. Use sqlite instead of postgresql
- [ ] 6a. Correction kickstart.json
- [x] 6a. Correction kickstart.json
- [ ] 6b. Parse payload of oidcModel since prisma using. search on github for the issue ticket
- [ ] 7. Making sure the tests are up to date
- [ ] 8a. Integration with minIO
- [x] 8a. Integration with minIO
- [x] 8b. Integration with oauth-2-proxy
- [ ] 9. Removing any redundant code and cleanup and documentation updation
- [ ] 10. otp integration
Expand Down Expand Up @@ -74,4 +74,11 @@ renderError page ki backchodi
[oidc.github p kuch mila](https://authts.github.io/oidc-client-ts/interfaces/OidcClientSettings.html)


http://localhost:3001/oidc/auth?client_id=myminioadmin&redirect_uri=http%3A%2F%2F192.168.250.157%3A9001%2Foauth_callback&response_type=code&scope=offline_access+openid&state=eyJzdGF0ZSI6IlIxSkJTakZVUjBaRVNrOUdORUUwU1RGT1NGQllWbE5JVGpwbFJuZzJTRWwyU0VOYVoxUTBVWFV3VURWU1N6WXJNVVpJVnpaS1RWUklVMWhIYTNCc1VVWnBkM1ZuUFE9PSIsImlkcF9uYW1lIjoiXyJ9
http://localhost:3001/oidc/auth?client_id=myminioadmin&redirect_uri=http%3A%2F%2F192.168.250.157%3A9001%2Foauth_callback&response_type=code&scope=offline_access+openid&state=eyJzdGF0ZSI6IlIxSkJTakZVUjBaRVNrOUdORUUwU1RGT1NGQllWbE5JVGpwbFJuZzJTRWwyU0VOYVoxUTBVWFV3VURWU1N6WXJNVVpJVnpaS1RWUklVMWhIYTNCc1VVWnBkM1ZuUFE9PSIsImlkcF9uYW1lIjoiXyJ9

domain pinning + non-consent screen +
1. oauth + with consent and without consent : DONE
2. tenant + application crud : DONE
3. domain pinnig
4. oauth2 proxy and minio : DONE
5. pkce : DONE

0 comments on commit ad9612b

Please sign in to comment.