Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adds azure identity capability #84

Merged
merged 2 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 35 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ To enable the provider, create or edit the file at `./config/plugins.js`.
This is an example `plugins.js` file for Azure storage:

```js

module.exports = ({ env }) => ({
upload: {
config: {
provider: "strapi-provider-upload-azure-storage",
providerOptions: {
authType: env("STORAGE_AUTH_TYPE", "default"),
account: env("STORAGE_ACCOUNT"),
accountKey: env("STORAGE_ACCOUNT_KEY"),//either account key or sas token is enough to make authentication
sasToken: env("STORAGE_ACCOUNT_SAS_TOKEN"),
Expand All @@ -55,19 +55,42 @@ module.exports = ({ env }) => ({
},
});

// For using azure identities, the correct authType is 'msi' or (provide it in the environment variable)

module.exports = ({ env }) => ({
upload: {
config: {
provider: "strapi-provider-upload-azure-storage",
providerOptions: {
authType: 'msi',
account: env("STORAGE_ACCOUNT"),
clientId: env("STORAGE_AZURE_CLIENT_ID"), // optional
serviceBaseURL: env("STORAGE_URL"), // optional
containerName: env("STORAGE_CONTAINER_NAME"),
defaultPath: "assets",
cdnBaseURL: env("STORAGE_CDN_URL"), // optional
defaultCacheControl: env("STORAGE_CACHE_CONTROL"), // optional
removeCN: env("REMOVE_CONTAINER_NAME"), // optional, if you want to remove container name from the URL
},
},
},
});

```

| Property | Required | Description |
| -------- | -------- | -------- |
| account | true | Azure account name |
| accountKey | true | Secret access key |
| sasToken | false | SAS Token, either accountKey or SASToken is required |
| serviceBaseURL | false | Base service URL to be used, optional. Defaults to `https://${account}.blob.core.windows.net` |
| containerName | true | Container name |
| defaultPath | true | The path to use when there is none being specified. Defaults to `assets` |
| cdnBaseURL | false | CDN base url |
| defaultCacheControl | false | Cache-Control header value for all uploaded files |
| removeCN | false | Set to true, to remove container name from azure URL |
| Property | Required | Description |
| ------------------- | ----------------------------------- | --------------------------------------------------------------------------------------------- |
| authType | true | Whether to use a SAS key ("default") or an identity ("msi") |
| account | true | Azure account name |
| accountKey | if 'authType 'default' | Secret access key |
| clientId | false (consumed if 'authType 'msi') | Azure Identity Client ID |
| sasToken | false | SAS Token, either accountKey or SASToken is required if 'authType is 'default' |
| serviceBaseURL | false | Base service URL to be used, optional. Defaults to `https://${account}.blob.core.windows.net` |
| containerName | true | Container name |
| defaultPath | true | The path to use when there is none being specified. Defaults to `assets` |
| cdnBaseURL | false | CDN base url |
| defaultCacheControl | false | Cache-Control header value for all uploaded files |
| removeCN | false | Set to true, to remove container name from azure URL |

### Security Middleware Configuration

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
"scripts": {
"build": "tsc -p tsconfig.json",
"lint": "eslint . --ext .js,.ts",
"lint:fix": "eslint . --ext .js,.ts --fix",
"prebuild": "rm -rf dist",
"prepublishOnly": "yarn build"
},
"directories": {
"lib": "./lib"
},
"dependencies": {
"@azure/identity": "^4.0.0",
"@azure/storage-blob": "^12.12.0"
},
"strapi": {
Expand Down
68 changes: 55 additions & 13 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DefaultAzureCredential } from '@azure/identity';
import {
AnonymousCredential,
BlobServiceClient,
Expand All @@ -6,10 +7,25 @@ import {
} from '@azure/storage-blob';
import internal from 'stream';

type Config = {
account: string;
type Config = DefaultConfig | ManagedIdentityConfig;

type DefaultConfig = {
authType: 'default';
accountKey: string;
sasToken: string;
account: string;
serviceBaseURL?: string;
containerName: string;
defaultPath: string;
cdnBaseURL?: string;
defaultCacheControl?: string;
removeCN?: string;
};

type ManagedIdentityConfig = {
authType: 'msi';
clientId?: string;
account: string;
serviceBaseURL?: string;
containerName: string;
defaultPath: string;
Expand Down Expand Up @@ -43,18 +59,36 @@ function getFileName(path: string, file: StrapiFile) {
}

function makeBlobServiceClient(config: Config) {
const account = trimParam(config.account);
const accountKey = trimParam(config.accountKey);
const sasToken = trimParam(config.sasToken);
const serviceBaseURL = getServiceBaseUrl(config);
// if accountKey doesn't contain value return below line
if (sasToken != '') {
const anonymousCredential = new AnonymousCredential();
return new BlobServiceClient(`${serviceBaseURL}${sasToken}`, anonymousCredential);

switch (config.authType) {
case 'default': {
const account = trimParam(config.account);
const accountKey = trimParam(config.accountKey);
const sasToken = trimParam(config.sasToken);
if (sasToken != '') {
const anonymousCredential = new AnonymousCredential();
return new BlobServiceClient(`${serviceBaseURL}${sasToken}`, anonymousCredential);
}
const sharedKeyCredential = new StorageSharedKeyCredential(account, accountKey);
const pipeline = newPipeline(sharedKeyCredential);
return new BlobServiceClient(serviceBaseURL, pipeline);
}
case 'msi': {
const clientId = trimParam(config.clientId);
if (clientId != null && clientId != '') {
return new BlobServiceClient(
serviceBaseURL,
new DefaultAzureCredential({ managedIdentityClientId: clientId })
);
}
return new BlobServiceClient(serviceBaseURL, new DefaultAzureCredential());
}
default: {
const exhaustiveCheck: never = config;
throw new Error(exhaustiveCheck);
}
}
const sharedKeyCredential = new StorageSharedKeyCredential(account, accountKey);
const pipeline = newPipeline(sharedKeyCredential);
return new BlobServiceClient(serviceBaseURL, pipeline);
}

const uploadOptions = {
Expand Down Expand Up @@ -109,12 +143,20 @@ async function handleDelete(
module.exports = {
provider: 'azure',
auth: {
authType: {
label: 'Authentication type (required, either "msi" or "default")',
type: 'text',
},
clientId: {
label: 'Azure Identity ClientId (consumed if authType is "msi" and passed as DefaultAzureCredential({ managedIdentityClientId: clientId }))',
type: 'text',
},
account: {
label: 'Account name (required)',
type: 'text',
},
accountKey: {
label: 'Secret access key (required)',
label: 'Secret access key (required if authType is "default")',
type: 'text',
},
serviceBaseURL: {
Expand Down
Loading