-
-
Notifications
You must be signed in to change notification settings - Fork 656
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: Implement multi token support for client tokens #1476
Conversation
This adds support for multi project tokens to be created. Backward compatibility is handled at 3 different layers here: - The API is made backwards compatible though a permissive data type that accepts either a project?: string or projects?: string[] property, validation is done through JOI here, which ensures that projects and project are not set together. In the case of neither, this defaults to the previous default of ALL_PROJECTS - The service layer method to handle adding tokens has been made tolerant to either of the above case and has been deprecated, a new method supporting only the new structure of using projects has been added - Existing compatibility for consumers of Unleash as a library should not be affected either, the ApiUser constructor is now tolerant to the the first input and will internally map to the new cleaned structure
This pull request is being automatically deployed with Vercel (learn more). 🔍 Inspect: https://vercel.com/unleash-team/unleash-docs/6ESBYF9gkHZTqwJCAAA1LVDmJ2mQ |
Coverage report
Show files with reduced coverage 🔻
Test suite run success842 tests passing in 121 suites. Report generated by 🧪jest coverage report action from e889d8e |
secret text NOT NULL, | ||
project text NOT NULL, | ||
FOREIGN KEY (secret) REFERENCES api_tokens (secret) ON DELETE CASCADE, | ||
FOREIGN KEY (project) REFERENCES projects(id) ON DELETE CASCADE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will this automatically remove the project from the token if the project is removed?
If yes: Awesome!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah it will!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cascade does rule :)
'seen_at', | ||
'environment', | ||
'token_project_link.project', | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am pretty sure this query can be written to actually return the list of projects as a string array directly from postgres using the "ARRAY_AGG" function. I am not sure if it is more efficient, but would at least avoid the need for the reducer processing the items.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh cool, I didn't actually know about ARRAY_AGG, that's super cool. I'll have to shuffle the query around but this shouldn't be a very hot path/large dataset so the readability wins out here for me anyway, thanks!
[newToken.secret, project], | ||
); | ||
}); | ||
await Promise.all(updateProjectTasks); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yo do not need to create a bunch of insert request. You just need to to map the projects in it to a list of "objects" matching the "API_LINK_TABLE" shape and perform a simple knex insert on all the values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What Ivar said, knex already has efficient batch insert, write the query, give a list of tuples with the same amount of args as your sql insert. See src/lib/db/clientMetricsStore-v2.ts:101 for instance.
Though this seems to have the advantage that it's transactional and as such will rollback the LINK_TABLE inserts if any of them fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that can be achieved anyway, and yes the transaction stuff is the important part here!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great thorough PR, I think Ivar's comments warrants a check to see if it's more efficient, but this LGTM :)
const randomStr = crypto.randomBytes(28).toString('hex'); | ||
return `${project}:${environment}.${randomStr}`; | ||
if (projects.length > 1) { | ||
return `[]:${environment}.${randomStr}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So multi project tokens now gets resolved from the db, fair enough :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As opposed to not being supported at all, don't get me wrong :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it's a big tradeoff here. All the other token types can be determined just from their secret signature, this is the first time they're no longer stateful. Means we can't yeet them from Unleash instance to Unleash instance unfortunately but it keeps the token small
); | ||
}; | ||
|
||
//This is a lossy down migration, tokens with multiple projects are discarded |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fair, I'd be a bit surprised if I had multple project tokens and then ended up with separate tokens for each project if I reverted the thing that allowed me to have a token accessing multiple projects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My thought too! I absolutely don't want them getting more permissive, and if you make them less permissive it's just weird. Clearly to destroy for everyone involved
@@ -198,7 +202,7 @@ const loadTokensFromString = (tokenString: String, tokenType: ApiTokenType) => { | |||
type: tokenType, | |||
username: 'admin', | |||
}; | |||
validateApiToken(token); | |||
validateApiToken(mapLegacyToken(token)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, so, we won't have support for adding multiproject tokens from environment variables for this release.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is correct! We need to come up with a separate format for the env vars, currently they're determined from the secret signature in this code path. The secret doesn't contain all the info needed for a multiproject. I think something like this would work:
We can read that string, calculate the projects and then strip them
[newToken.secret, project], | ||
); | ||
}); | ||
await Promise.all(updateProjectTasks); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What Ivar said, knex already has efficient batch insert, write the query, give a list of tuples with the same amount of args as your sql insert. See src/lib/db/clientMetricsStore-v2.ts:101 for instance.
Though this seems to have the advantage that it's transactional and as such will rollback the LINK_TABLE inserts if any of them fail.
secret text NOT NULL, | ||
project text NOT NULL, | ||
FOREIGN KEY (secret) REFERENCES api_tokens (secret) ON DELETE CASCADE, | ||
FOREIGN KEY (project) REFERENCES projects(id) ON DELETE CASCADE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cascade does rule :)
This adds support for multi project tokens to be created. Backward compatibility is handled at 3 different layers here: