Skip to content

Commit

Permalink
0.0.4
Browse files Browse the repository at this point in the history
  • Loading branch information
alexi committed Aug 9, 2022
1 parent b124908 commit e81ee72
Show file tree
Hide file tree
Showing 11 changed files with 600 additions and 3 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ test/__app
*.sublime-project
*.sublime-workspace
*.log
build
dist
# build
# dist
shrinkwrap.yaml
17 changes: 17 additions & 0 deletions build/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
The package has been configured successfully!

Make sure to first define the mapping inside the `contracts/ally.ts` file as follows.

```ts
import { OktaDriver, OktaDriverConfig } from 'ally-custom-driver/build/standalone'

declare module '@ioc:Adonis/Addons/Ally' {
interface SocialProviders {
// ... other mappings
OktaDriver: {
config: OktaDriverConfig
implementation: OktaDriver
}
}
}
```
7 changes: 7 additions & 0 deletions build/providers/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/// <reference types="@adonisjs/application/build/adonis-typings" />
import type { ApplicationContract } from '@ioc:Adonis/Core/Application';
export default class OktaDriverProvider {
protected app: ApplicationContract;
constructor(app: ApplicationContract);
boot(): Promise<void>;
}
38 changes: 38 additions & 0 deletions build/providers/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
class OktaDriverProvider {
constructor(app) {
this.app = app;
}
async boot() {
const Ally = this.app.container.resolveBinding('Adonis/Addons/Ally');
const { OktaDriver } = await Promise.resolve().then(() => __importStar(require('../src/Okta')));
Ally.extend('Okta', (_, __, config, ctx) => {
return new OktaDriver(ctx, config);
});
}
}
exports.default = OktaDriverProvider;
125 changes: 125 additions & 0 deletions build/src/Okta/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/// <reference types="@adonisjs/ally" />
/// <reference types="@adonisjs/http-server/build/adonis-typings" />
/// <reference types="@adonisjs/ally" />
import type { AllyUserContract } from '@ioc:Adonis/Addons/Ally';
import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext';
import { Oauth2Driver, ApiRequest } from '@adonisjs/ally/build/standalone';
import { UserFields } from '../helpers/AllyUser';
export declare type OktaDriverAccessToken = {
token: string;
type: 'bearer';
};
export declare type OktaDriverScopes = 'openid' | 'profile' | 'email';
export declare type OktaDriverConfig = {
driver: 'Okta';
clientId: string;
callbackUrl: string;
clientSecret: string;
issuer: string;
domain: string;
scopes: OktaDriverScopes[];
};
export declare type UserInfo = {
sub: string;
name: string;
locale: string;
email: string;
preferred_username: string;
given_name: string;
family_name: string;
zoneinfo: string;
email_verified: boolean;
};
/**
* Driver implementation. It is mostly configuration driven except the user calls
*/
export declare class OktaDriver extends Oauth2Driver<OktaDriverAccessToken, OktaDriverScopes> {
config: OktaDriverConfig;
/**
* The URL for the redirect request. The user will be redirected on this page
* to authorize the request.
*
* Do not define query strings in this URL.
*/
protected authorizeUrl: string;
/**
* The URL to hit to exchange the authorization code for the access token
*
* Do not define query strings in this URL.
*/
protected accessTokenUrl: string;
/**
* The URL to hit to get the user details
*
* Do not define query strings in this URL.
*/
protected userInfoUrl: string;
/**
* The param name for the authorization code. Read the documentation of your oauth
* provider and update the param name to match the query string field name in
* which the oauth provider sends the authorization_code post redirect.
*/
protected codeParamName: string;
/**
* The param name for the error. Read the documentation of your oauth provider and update
* the param name to match the query string field name in which the oauth provider sends
* the error post redirect
*/
protected errorParamName: string;
/**
* Cookie name for storing the CSRF token. Make sure it is always unique. So a better
* approach is to prefix the oauth provider name to `oauth_state` value. For example:
* For example: "facebook_oauth_state"
*/
protected stateCookieName: string;
/**
* Parameter name to be used for sending and receiving the state from.
* Read the documentation of your oauth provider and update the param
* name to match the query string used by the provider for exchanging
* the state.
*/
protected stateParamName: string;
/**
* Parameter name for sending the scopes to the oauth provider.
*/
protected scopeParamName: string;
/**
* The separator indentifier for defining multiple scopes
*/
protected scopesSeparator: string;
protected issuer: string;
protected domain: string;
protected clientId: string;
constructor(ctx: HttpContextContract, config: OktaDriverConfig);
/**
* Optionally configure the authorization redirect request. The actual request
* is made by the base implementation of "Oauth2" driver and this is a
* hook to pre-configure the request.
*/
/**
* Optionally configure the access token request. The actual request is made by
* the base implementation of "Oauth2" driver and this is a hook to pre-configure
* the request
*/
/**
* Update the implementation to tell if the error received during redirect
* means "ACCESS DENIED".
*/
accessDenied(): boolean;
getCode(): string | null;
protected buildAllyUser(userProfile: any, accessTokenResponse: any): UserFields;
/**
* Get the user details by query the provider API. This method must return
* the access token and the user details both. Checkout the google
* implementation for same.
*
* https://github.com/adonisjs/ally/blob/develop/src/Drivers/Google/index.ts#L191-L199
*/
user(_callback?: (request: ApiRequest) => void): Promise<AllyUserContract<OktaDriverAccessToken>>;
/**
* Returns the HTTP request with the authorization header set
*/
protected getAuthenticatedRequest(url: string, token: string): ApiRequest;
protected getUserInfo(accessToken: string, callback?: (request: ApiRequest) => void): Promise<UserFields>;
userFromToken(accessToken: string): Promise<AllyUserContract<OktaDriverAccessToken>>;
}
199 changes: 199 additions & 0 deletions build/src/Okta/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
"use strict";
/*
|--------------------------------------------------------------------------
| Ally Oauth driver
|--------------------------------------------------------------------------
|
| This is a dummy implementation of the Oauth driver. Make sure you
|
| - Got through every line of code
| - Read every comment
|
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OktaDriver = void 0;
const standalone_1 = require("@adonisjs/ally/build/standalone");
const jwt_verifier_1 = __importDefault(require("@okta/jwt-verifier"));
const lodash_1 = __importDefault(require("lodash"));
const AllyUser_1 = __importDefault(require("../helpers/AllyUser"));
/**
* Driver implementation. It is mostly configuration driven except the user calls
*/
class OktaDriver extends standalone_1.Oauth2Driver {
constructor(ctx, config) {
super(ctx, config);
this.config = config;
/**
* The URL for the redirect request. The user will be redirected on this page
* to authorize the request.
*
* Do not define query strings in this URL.
*/
this.authorizeUrl = '';
/**
* The URL to hit to exchange the authorization code for the access token
*
* Do not define query strings in this URL.
*/
this.accessTokenUrl = '';
/**
* The URL to hit to get the user details
*
* Do not define query strings in this URL.
*/
this.userInfoUrl = '';
/**
* The param name for the authorization code. Read the documentation of your oauth
* provider and update the param name to match the query string field name in
* which the oauth provider sends the authorization_code post redirect.
*/
this.codeParamName = 'code';
/**
* The param name for the error. Read the documentation of your oauth provider and update
* the param name to match the query string field name in which the oauth provider sends
* the error post redirect
*/
this.errorParamName = 'error';
/**
* Cookie name for storing the CSRF token. Make sure it is always unique. So a better
* approach is to prefix the oauth provider name to `oauth_state` value. For example:
* For example: "facebook_oauth_state"
*/
this.stateCookieName = 'OktaDriver_oauth_state';
/**
* Parameter name to be used for sending and receiving the state from.
* Read the documentation of your oauth provider and update the param
* name to match the query string used by the provider for exchanging
* the state.
*/
this.stateParamName = 'state';
/**
* Parameter name for sending the scopes to the oauth provider.
*/
this.scopeParamName = 'scope';
/**
* The separator indentifier for defining multiple scopes
*/
this.scopesSeparator = ',';
this.issuer = '';
this.domain = '';
this.clientId = '';
/**
* Extremely important to call the following method to clear the
* state set by the redirect request.
*
* DO NOT REMOVE THE FOLLOWING LINE
*/
this.loadState();
this.domain = config.domain;
this.issuer = config.issuer;
this.clientId = config.clientId;
// this.callback = config.redirectUri
}
/**
* Optionally configure the authorization redirect request. The actual request
* is made by the base implementation of "Oauth2" driver and this is a
* hook to pre-configure the request.
*/
// protected configureRedirectRequest(request: RedirectRequest<OktaDriverScopes>) {}
/**
* Optionally configure the access token request. The actual request is made by
* the base implementation of "Oauth2" driver and this is a hook to pre-configure
* the request
*/
// protected configureAccessTokenRequest(request: ApiRequest) {}
/**
* Update the implementation to tell if the error received during redirect
* means "ACCESS DENIED".
*/
accessDenied() {
return this.ctx.request.input('error') === 'user_denied';
}
getCode() {
return this.ctx.request.input(this.codeParamName, null);
}
buildAllyUser(userProfile, accessTokenResponse) {
const allyUserBuilder = new AllyUser_1.default();
const expires = lodash_1.default.get(accessTokenResponse, 'expiresAt');
allyUserBuilder
.setOriginal(userProfile)
.setFields(userProfile.sub, userProfile.given_name, userProfile.family_name, userProfile.email, null, userProfile.email_verified ? 'verified' : 'unverified')
.setToken(accessTokenResponse.accessToken, null, null, expires ? Number(expires) : null);
const user = allyUserBuilder.toJSON();
return user;
}
/**
* Get the user details by query the provider API. This method must return
* the access token and the user details both. Checkout the google
* implementation for same.
*
* https://github.com/adonisjs/ally/blob/develop/src/Drivers/Google/index.ts#L191-L199
*/
async user(_callback) {
const accessTokenResponse = this.ctx.request.input('accessToken');
const errorMessage = 'Okta login failed';
if (!accessTokenResponse)
throw new Error(errorMessage);
const audience = this.ctx.request.input('oktaAud') || 'api://default';
if (this.ctx.request.input('oktaDomain') && this.ctx.request.input('oktaClientId')) {
this.issuer =
this.ctx.request.input('oktaIssuer') ||
`https://${this.ctx.request.input('oktaDomain')}/oauth2/default`;
this.domain = this.ctx.request.input('oktaDomain');
this.clientId = this.ctx.request.input('oktaClientId');
}
try {
const oktaJwtVerifier = new jwt_verifier_1.default({
issuer: this.issuer,
clientId: this.clientId,
assertClaims: {
cid: this.clientId,
aud: audience,
},
});
await oktaJwtVerifier.verifyAccessToken(accessTokenResponse.value, audience);
const userProfile = await this.getUserInfo(accessTokenResponse.value);
return {
...userProfile,
token: accessTokenResponse.value,
};
}
catch (e) {
throw new Error(errorMessage);
}
}
/**
* Returns the HTTP request with the authorization header set
*/
getAuthenticatedRequest(url, token) {
const request = this.httpClient(url);
request.header('Authorization', `Bearer ${token}`);
request.header('Accept', 'application/json');
request.header('x-li-format', 'json');
request.parseAs('json');
return request;
}
async getUserInfo(accessToken, callback) {
// User Info
const userRequest = this.getAuthenticatedRequest(`${this.issuer}/v1/userinfo`, accessToken);
const accessTokenResponse = {
accessToken,
};
if (typeof callback === 'function') {
callback(userRequest);
}
const userBody = await userRequest.get();
return this.buildAllyUser(userBody, accessTokenResponse);
}
async userFromToken(accessToken) {
const user = {};
return {
...user,
token: { token: accessToken, type: 'bearer' },
};
}
}
exports.OktaDriver = OktaDriver;
Loading

0 comments on commit e81ee72

Please sign in to comment.